redis基础以及高频使用场景详解

Redis 基础以及高频使用场景详解

目录

Redis简介

什么是Redis

Redis(Remote Dictionary Server)是一个开源的、基于内存的数据结构存储系统,可以用作数据库、缓存和消息中间件。它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,并提供了丰富的操作命令。

主要特点

  • 高性能: 基于内存操作,读写性能极高
  • 数据结构丰富: 支持字符串、哈希、列表、集合、有序集合等
  • 持久化: 支持RDB和AOF两种持久化方式
  • 主从复制: 支持主从复制和哨兵模式
  • 集群模式: 支持Redis Cluster分布式集群
  • 事务支持: 支持简单的事务操作
  • Lua脚本: 支持Lua脚本执行

适用场景

  • 缓存系统
  • 会话存储
  • 排行榜系统
  • 消息队列
  • 分布式锁
  • 计数器系统
  • 实时统计

核心概念

数据结构

Redis数据结构
├── String - 字符串类型
├── Hash - 哈希类型
├── List - 列表类型
├── Set - 集合类型
├── Sorted Set - 有序集合类型
├── Bitmap - 位图类型
├── HyperLogLog - 基数统计类型
└── Stream - 流类型

内存模型

Redis内存结构
├── 数据内存 - 存储实际数据
├── 索引内存 - 存储索引信息
├── 缓冲区内存 - 客户端缓冲区
└── 其他内存 - 系统开销

使用技巧

1. 字符串操作

# 基本操作
SET key value
GET key
DEL key
EXISTS key

# 批量操作
MSET key1 value1 key2 value2
MGET key1 key2

# 数值操作
INCR key
INCRBY key increment
DECR key
DECRBY key decrement

# 过期时间
EXPIRE key seconds
TTL key
PERSIST key

2. 哈希操作

# 基本操作
HSET key field value
HGET key field
HDEL key field
HEXISTS key field

# 批量操作
HMSET key field1 value1 field2 value2
HMGET key field1 field2
HGETALL key

# 数值操作
HINCRBY key field increment
HINCRBYFLOAT key field increment

3. 列表操作

# 基本操作
LPUSH key value
RPUSH key value
LPOP key
RPOP key
LRANGE key start stop

# 阻塞操作
BLPOP key timeout
BRPOP key timeout

# 列表维护
LTRIM key start stop
LLEN key
LINDEX key index

4. 集合操作

# 基本操作
SADD key member
SREM key member
SISMEMBER key member
SMEMBERS key

# 集合运算
SINTER key1 key2
SUNION key1 key2
SDIFF key1 key2

# 集合统计
SCARD key
SRANDMEMBER key count

5. 有序集合操作

# 基本操作
ZADD key score member
ZREM key member
ZSCORE key member
ZRANK key member

# 范围查询
ZRANGE key start stop [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES]
ZRANGEBYSCORE key min max [WITHSCORES]

# 统计操作
ZCARD key
ZCOUNT key min max

6. 高级特性

# 发布订阅
PUBLISH channel message
SUBSCRIBE channel
UNSUBSCRIBE channel

# 事务操作
MULTI
SET key1 value1
SET key2 value2
EXEC

# Lua脚本
EVAL "return redis.call('GET', KEYS[1])" 1 key1

重难点解析

1. 持久化机制

# RDB持久化配置
save 900 1      # 900秒内至少1个key变化
save 300 10     # 300秒内至少10个key变化
save 60 10000   # 60秒内至少10000个key变化

# AOF持久化配置
appendonly yes
appendfsync always    # 每次写入都同步
appendfsync everysec  # 每秒同步一次
appendfsync no        # 由操作系统决定

# 混合持久化
aof-use-rdb-preamble yes

2. 内存管理

# 内存策略配置
maxmemory 2gb
maxmemory-policy allkeys-lru    # 最近最少使用
# 其他策略: volatile-lru, allkeys-random, volatile-random, volatile-ttl, noeviction

# 内存优化
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

3. 主从复制

# 主节点配置
bind 0.0.0.0
port 6379
daemonize yes

# 从节点配置
slaveof master_ip master_port
slave-read-only yes
slave-priority 100

# 复制优化
repl-backlog-size 1mb
repl-backlog-ttl 3600

4. 哨兵模式

# 哨兵配置
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1

# 启动哨兵
redis-sentinel sentinel.conf

5. 集群模式

# 集群配置
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-migration-barrier 1
cluster-require-full-coverage yes

# 创建集群
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1

Spring Boot集成

1. 依赖配置

<!-- Maven -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
// Gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.apache.commons:commons-pool2'

2. 配置文件

# application.yml
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
  
    # 连接池配置
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms
  
    # 集群配置
    # cluster:
    #   nodes:
    #     - 127.0.0.1:7000
    #     - 127.0.0.1:7001
    #     - 127.0.0.1:7002
    #   max-redirects: 3
  
    # 哨兵配置
    # sentinel:
    #   master: mymaster
    #   nodes:
    #     - 127.0.0.1:26379
    #     - 127.0.0.1:26380
    #     - 127.0.0.1:26381

3. 配置类

@Configuration
@EnableCaching
public class RedisConfig {
  
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
      
        // 设置key序列化器
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
      
        // 设置value序列化器
        Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, 
            ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jsonSerializer.setObjectMapper(objectMapper);
      
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
      
        template.afterPropertiesSet();
        return template;
    }
  
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
      
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(config)
            .build();
    }
}

4. 服务层实现

@Service
public class UserService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
  
    // 基本操作
    public void setUser(String key, User user) {
        redisTemplate.opsForValue().set(key, user, Duration.ofHours(1));
    }
  
    public User getUser(String key) {
        return (User) redisTemplate.opsForValue().get(key);
    }
  
    // 哈希操作
    public void setUserHash(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }
  
    public Object getUserHash(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }
  
    // 列表操作
    public void addToList(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
    }
  
    public List<Object> getList(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
  
    // 集合操作
    public void addToSet(String key, Object value) {
        redisTemplate.opsForSet().add(key, value);
    }
  
    public Set<Object> getSet(String key) {
        return redisTemplate.opsForSet().members(key);
    }
  
    // 有序集合操作
    public void addToSortedSet(String key, Object value, double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }
  
    public Set<Object> getTopFromSortedSet(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }
  
    // 分布式锁
    public boolean acquireLock(String lockKey, String requestId, long expireTime) {
        String script = "if redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2], 'NX') then return 1 else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
      
        Long result = redisTemplate.execute(redisScript, Arrays.asList(lockKey), requestId, expireTime);
        return result != null && result == 1L;
    }
  
    public boolean releaseLock(String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
      
        Long result = redisTemplate.execute(redisScript, Arrays.asList(lockKey), requestId);
        return result != null && result == 1L;
    }
  
    // 缓存注解
    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        // 从数据库查询用户
        return userRepository.findById(id);
    }
  
    @CacheEvict(value = "users", key = "#user.id")
    public void updateUser(User user) {
        userRepository.save(user);
    }
}

5. 控制器层

@RestController
@RequestMapping("/api/users")
public class UserController {
  
    @Autowired
    private UserService userService;
  
    @PostMapping("/cache")
    public ResponseEntity<Void> cacheUser(@RequestBody User user) {
        userService.setUser("user:" + user.getId(), user);
        return ResponseEntity.ok().build();
    }
  
    @GetMapping("/cache/{id}")
    public ResponseEntity<User> getCachedUser(@PathVariable String id) {
        User user = userService.getUser("user:" + id);
        return ResponseEntity.ok(user);
    }
  
    @PostMapping("/lock")
    public ResponseEntity<String> acquireLock(@RequestParam String key) {
        String requestId = UUID.randomUUID().toString();
        boolean acquired = userService.acquireLock(key, requestId, 30);
        if (acquired) {
            return ResponseEntity.ok(requestId);
        } else {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("Lock acquisition failed");
        }
    }
  
    @DeleteMapping("/lock")
    public ResponseEntity<Void> releaseLock(@RequestParam String key, @RequestParam String requestId) {
        boolean released = userService.releaseLock(key, requestId);
        if (released) {
            return ResponseEntity.ok().build();
        } else {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
        }
    }
}

分布式场景使用

1. 分布式锁

@Component
public class DistributedLockService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    private static final String LOCK_PREFIX = "lock:";
    private static final long DEFAULT_EXPIRE_TIME = 30;
  
    /**
     * 获取分布式锁
     */
    public boolean acquireLock(String lockKey, String requestId, long expireTime) {
        String key = LOCK_PREFIX + lockKey;
        String script = "if redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2], 'NX') then return 1 else return 0 end";
      
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
      
        Long result = redisTemplate.execute(redisScript, Arrays.asList(key), requestId, expireTime);
        return result != null && result == 1L;
    }
  
    /**
     * 释放分布式锁
     */
    public boolean releaseLock(String lockKey, String requestId) {
        String key = LOCK_PREFIX + lockKey;
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
      
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
      
        Long result = redisTemplate.execute(redisScript, Arrays.asList(key), requestId);
        return result != null && result == 1L;
    }
  
    /**
     * 可重入锁
     */
    public boolean acquireReentrantLock(String lockKey, String requestId, long expireTime) {
        String key = LOCK_PREFIX + lockKey;
        String script = "if redis.call('exists', KEYS[1]) == 0 then " +
                       "redis.call('hset', KEYS[1], ARGV[1], 1) " +
                       "redis.call('expire', KEYS[1], ARGV[2]) " +
                       "return 1 " +
                       "end " +
                       "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
                       "redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                       "redis.call('expire', KEYS[1], ARGV[2]) " +
                       "return 1 " +
                       "end " +
                       "return 0";
      
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
      
        Long result = redisTemplate.execute(redisScript, Arrays.asList(key), requestId, expireTime);
        return result != null && result == 1L;
    }
}

2. 分布式缓存

@Service
public class DistributedCacheService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    /**
     * 设置缓存
     */
    public void set(String key, Object value, Duration timeout) {
        redisTemplate.opsForValue().set(key, value, timeout);
    }
  
    /**
     * 获取缓存
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
  
    /**
     * 删除缓存
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }
  
    /**
     * 批量删除缓存
     */
    public void deleteBatch(Collection<String> keys) {
        redisTemplate.delete(keys);
    }
  
    /**
     * 设置缓存并设置过期时间
     */
    public void setWithExpire(String key, Object value, Duration timeout) {
        redisTemplate.opsForValue().set(key, value, timeout);
    }
  
    /**
     * 缓存穿透保护
     */
    public Object getWithProtection(String key, Supplier<Object> loader, Duration timeout) {
        Object value = get(key);
        if (value != null) {
            return value;
        }
      
        // 防止缓存穿透
        String lockKey = "lock:" + key;
        String requestId = UUID.randomUUID().toString();
      
        try {
            if (acquireLock(lockKey, requestId, 10)) {
                // 双重检查
                value = get(key);
                if (value != null) {
                    return value;
                }
              
                // 从数据源加载
                value = loader.get();
                if (value != null) {
                    set(key, value, timeout);
                } else {
                    // 设置空值防止缓存穿透
                    set(key, new NullValue(), Duration.ofMinutes(5));
                }
                return value;
            } else {
                // 等待其他线程加载完成
                Thread.sleep(100);
                return get(key);
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to get cache", e);
        } finally {
            releaseLock(lockKey, requestId);
        }
    }
}

3. 分布式限流

@Component
public class RateLimiterService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    /**
     * 滑动窗口限流
     */
    public boolean isAllowed(String key, int maxRequests, Duration window) {
        String script = "local current = redis.call('get', KEYS[1]) " +
                       "if current == false then " +
                       "redis.call('setex', KEYS[1], ARGV[2], 1) " +
                       "return 1 " +
                       "end " +
                       "if tonumber(current) < tonumber(ARGV[1]) then " +
                       "redis.call('incr', KEYS[1]) " +
                       "return 1 " +
                       "end " +
                       "return 0";
      
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
      
        Long result = redisTemplate.execute(redisScript, Arrays.asList(key), maxRequests, window.getSeconds());
        return result != null && result == 1L;
    }
  
    /**
     * 令牌桶限流
     */
    public boolean isAllowedTokenBucket(String key, int capacity, int rate, Duration window) {
        String script = "local current = redis.call('get', KEYS[1]) " +
                       "local now = ARGV[1] " +
                       "local last = ARGV[2] " +
                       "local rate = tonumber(ARGV[3]) " +
                       "local capacity = tonumber(ARGV[4]) " +
                       "local tokens = tonumber(current or capacity) " +
                       "local elapsed = now - last " +
                       "tokens = math.min(capacity, tokens + elapsed * rate) " +
                       "if tokens >= 1 then " +
                       "tokens = tokens - 1 " +
                       "redis.call('setex', KEYS[1], ARGV[5], tokens) " +
                       "redis.call('setex', KEYS[2], ARGV[5], now) " +
                       "return 1 " +
                       "end " +
                       "return 0";
      
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
      
        long now = System.currentTimeMillis();
        long last = getLastRefillTime(key + ":last");
      
        Long result = redisTemplate.execute(redisScript, 
            Arrays.asList(key, key + ":last"), 
            now, last, rate, capacity, window.getSeconds());
      
        return result != null && result == 1L;
    }
  
    private long getLastRefillTime(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        return value != null ? (Long) value : System.currentTimeMillis();
    }
}

4. 分布式会话

@Configuration
public class SessionConfig {
  
    @Bean
    public RedisSessionRepository redisSessionRepository(RedisConnectionFactory connectionFactory) {
        return new RedisSessionRepository(connectionFactory);
    }
  
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

@Component
public class SessionService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    private static final String SESSION_PREFIX = "session:";
  
    /**
     * 创建会话
     */
    public String createSession(String userId, Map<String, Object> attributes, Duration timeout) {
        String sessionId = UUID.randomUUID().toString();
        String key = SESSION_PREFIX + sessionId;
      
        Map<String, Object> sessionData = new HashMap<>();
        sessionData.put("userId", userId);
        sessionData.put("attributes", attributes);
        sessionData.put("createdAt", System.currentTimeMillis());
      
        redisTemplate.opsForValue().set(key, sessionData, timeout);
        return sessionId;
    }
  
    /**
     * 获取会话
     */
    public Map<String, Object> getSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        return (Map<String, Object>) redisTemplate.opsForValue().get(key);
    }
  
    /**
     * 更新会话
     */
    public void updateSession(String sessionId, Map<String, Object> attributes, Duration timeout) {
        String key = SESSION_PREFIX + sessionId;
        Map<String, Object> sessionData = getSession(sessionId);
        if (sessionData != null) {
            sessionData.put("attributes", attributes);
            sessionData.put("updatedAt", System.currentTimeMillis());
            redisTemplate.opsForValue().set(key, sessionData, timeout);
        }
    }
  
    /**
     * 删除会话
     */
    public void deleteSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        redisTemplate.delete(key);
    }
}

最佳实践

1. 性能优化

  • 连接池配置: 合理设置连接池大小
  • 批量操作: 使用pipeline和multi-exec
  • 序列化优化: 选择合适的序列化方式
  • 内存优化: 合理设置内存策略

2. 高可用配置

  • 主从复制: 配置主从节点
  • 哨兵模式: 自动故障转移
  • 集群模式: 水平扩展和高可用
  • 持久化: 配置RDB和AOF

3. 安全配置

  • 密码认证: 设置访问密码
  • 网络隔离: 限制网络访问
  • 权限控制: 配置用户权限
  • TLS加密: 启用SSL/TLS

4. 监控维护

  • 性能监控: 监控响应时间和吞吐量
  • 内存监控: 监控内存使用情况
  • 连接监控: 监控连接数和连接状态
  • 日志分析: 分析慢查询和错误日志

总结

Redis作为高性能的内存数据库,具有丰富的数据结构、强大的缓存能力和完善的分布式特性。通过合理的配置和优化,可以构建高性能、高可用的缓存和存储系统。

关键要点:

  1. 理解Redis的数据结构和操作命令
  2. 掌握持久化和内存管理机制
  3. 熟练配置主从复制和集群模式
  4. 正确集成Spring Boot应用
  5. 合理使用分布式锁、缓存、限流等特性
  6. 遵循最佳实践确保性能和稳定性

通过本指南的学习,您应该能够熟练使用Redis并成功集成到Spring Boot项目中,构建高性能的分布式应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值