一文读懂redis,历时两个半月,强力推荐

Redis 全面指南

Redis 全面指南

  1. Redis 基础

1.1 Redis 简介

1.2 Redis与传统数据库的比较

1.3 Redis主要应用场景

1.4 Redis安装和配置

1.4.1 Linux安装Redis

1.4.2 Docker安装Redis

1.4.3 基本配置参数

1.5 Redis基本操作

1.5.1 连接Redis

1.5.2 基本命令示例

  1. Redis 与 Spring Boot 集成

2.1 Maven依赖

2.2 基本配置

2.3 Redis Template 配置

2.4 基本操作示例

2.4.1 使用RedisTemplate

2.4.2 使用StringRedisTemplate

2.5 操作不同数据类型

2.5.1 操作List

2.5.2 操作Hash

2.5.3 操作Set

2.5.4 操作Sorted Set

  1. Redis 数据类型和操作

3.1 字符串(String)

3.1.1 基本命令

3.1.2 应用场景

3.1.3 Spring Boot示例

3.2 哈希(Hash)

3.2.1 基本命令

3.2.2 应用场景

3.2.3 Spring Boot示例

3.3 列表(List)

3.3.1 基本命令

3.3.2 应用场景

3.3.3 Spring Boot示例

3.4 集合(Set)

3.4.1 基本命令

3.4.2 应用场景

3.4.3 Spring Boot示例

3.5 有序集合(Sorted Set)

3.5.1 基本命令

3.5.2 应用场景

3.5.3 Spring Boot示例

  1. Redis 高级特性

4.1 事务

4.1.1 基本命令

4.1.2 特点与限制

4.1.3 Spring Boot示例

4.2 发布订阅

4.2.1 基本命令4.2.2 应用场景

4.2.3 Spring Boot示例

4.3 Lua脚本

4.3.1 基本命令

4.3.2 脚本示例

4.3.3 Spring Boot示例

4.4 持久化

4.4.1 RDB(Redis Database)

4.4.2 AOF(Append Only File)

4.4.3 混合持久化

4.5 集群与高可用

4.5.1 主从复制

4.5.2 Redis Sentinel

4.5.3 Redis Cluster

4.5.4 Spring Boot配置示例

4.6 Redis 6.0+新特性

4.6.1 访问控制列表(ACL)

4.6.2 客户端缓存

4.6.3 多线程I/O

4.6.4 RESP3协议

4.6.5 SSL增强

4.6.6 有序集合(ZSET)增强功能

5 Redis****缓存问题与解决方案

5.1 缓存穿透

5.1.1 问题描述

5.1.2 解决方案

5.2 缓存击穿

5.2.1 问题描述

5.2.2 解决方案

5.3 缓存雪崩

5.3.1 问题描述

5.3.2 解决方案

5.4 缓存一致性

5.4.1 问题描述

5.4.2 解决方案

5.5 热点数据处理

5.5.1 问题描述

5.5.2 解决方案

5.6 大key问题

5.6.1 问题描述

5.6.2 解决方案

5.7 Redis性能优化

5.7.1 连接池优化

5.7.2 批量操作优化

5.7.3 数据结构选择

5.7.4 内存优化

5.8 Redis监控与运维

5.8.1 监控指标

5.8.2 监控工具

5.8.3 持久化策略调优

5.8.4 备份与恢复

5.8.5 扩容与缩容

  1. Redis 与 Spring Boot 集成的高级特性

6.1 Spring Cache集成Redis

6.1.1 基本配置

6.1.2 缓存注解使用6.2 分布式锁

6.2.1 基于Redisson的分布式锁

6.2.2 可重入锁

6.2.3 公平锁

6.2.4 读写锁

6.2.5 信号量

6.3 布隆过滤器

6.4 批量操作与管道

6.5 GEO地理位置

6.6 实时统计与计数

6.7 Redis Stream

  1. Redis 性能优化建议

7.1 连接管理优化

7.1.1 使用连接池

7.1.2 连接池参数优化建议

7.2 命令执行优化

7.2.1 批量操作

7.2.2 使用Pipeline

7.3 键设计优化

7.3.1 键命名规范

7.3.2 避免使用过长的键

7.3.3 使用合适的数据结构

7.4 内存优化

7.4.1 序列化优化

7.4.2 优化键值大小

  1. Redis 实际项目案例分析

8.1 商城秒杀系统

8.1.1 系统架构设计

8.1.2 Redis在秒杀系统中的应用

8.1.3 秒杀系统的性能优化

8.2 社交网络应用

8.2.1 用户关系存储

8.2.2 Feed流实现

  1. Redis 高级特性与运维

9.1 Redis事务

9.1.1 基本事务操作

9.1.2 在Spring Boot中使用Redis事务

9.1.3 事务的局限性

9.2 Redis管道(Pipeline)

9.2.1 管道与事务的区别

9.2.2 在Spring Boot中使用管道

9.3 Redis发布/订阅模式

9.3.1 基本命令

9.3.2 在Spring Boot中实现发布/订阅

9.4 Redis Lua脚本

9.4.1 基本用法

9.4.2 在Spring Boot中使用Lua脚本

9.5 Redis Stream

9.5.1 基本命令

9.5.2 在Spring Boot中使用Stream

9.6 持久化策略调优

9.7 高可用部署

9.7.1 主从复制

9.7.2 Redis Sentinel

9.7.3 Redis Cluster

9.8 安全性9.8.1 设置密码

9.8.2 使用SSL加密

9.8.3 网络安全

9.9 监控与优化

9.9.1 关键指标监控

9.9.2 使用Redis Exporter与Prometheus监控

9.9.3 常见性能问题与解决方案

9.10 分布式锁最佳实践

9.10.1 基于SET NX实现分布式锁

9.10.2 使用Redisson实现分布式锁

9.11 Redis使用常见陷阱与避坑指南

9.11.1 避免使用O(N)复杂度的命令

9.11.2 合理使用数据结构

9.11.3 避免缓存雪崩和缓存击穿

9.12 性能测试与基准测试

9.12.1 使用redis-benchmark工具

9.12.2 使用Spring Boot应用进行基准测试

总结

  1. Redis 实战:常见问题与解决方案

10.1 内存优化

10.1.1 设置合理的maxmemory和内存淘汰策略

10.1.2 避免使用大对象

10.1.3 启用Redis压缩

10.2 持久化策略

10.2.1 RDB与AOF的选择

10.2.2 持久化性能优化

10.3 高可用部署

10.4 其他问题与解决方案

10.4.1 Redis与数据库一致性

10.4.2 Redis连接问题

10.4.3 Redis数据迁移

  1. Redis 扩展与生态系统

11.1 Redis模块扩展

11.1.1 RediSearch

11.1.2 RedisTimeSeries

11.1.3 RedisJSON

11.2 Redis可视化工具

11.2.1 Redis Desktop Manager

11.2.2 Redis Commander

11.2.3 RedisInsight

11.3 Redis与Spring Data

11.3.1 使用Spring Data Redis Repositories

11.3.2 Spring Cache与Redis

11.4 Redis与Spring Session

11.4.1 基本配置

11.4.2 自定义会话序列化

11.4.3 会话事件监听

11.5 Redis与Spring Integration

11.5.1 消息通道配置

11.5.2 消息处理器

11.6 Redis与SpringBoot Actuator

11.6.1 基本配置

11.6.2 自定义健康指标

11.7 Redis生态系统中的其他工具

11.7.1 Redisson

11.7.2 Lettuce11.7.3 Redis Sentinel与Redis Cluster客户端

11.8 Redis与Spring Cloud

11.8.1 配置中心

11.8.2 服务发现

11.8.3 分布式锁和分布式会话

总结

  1. Redis 与 Spring Boot 实战案例

12.1 商城秒杀系统

12.1.1 系统架构设计

12.1.2 Redis在秒杀系统中的应用

12.1.3 秒杀系统的性能优化

12.2 社交网络应用

12.2.1 用户关系存储

12.2.2 Feed流实现

12.4 社交网络动态流系统

12.4.1 系统需求

12.4.2 数据模型设计

12.4.3 Redis数据结构设计

12.4.4 系统架构与实现

12.4.5 性能优化

12.4.6 扩展能力

12.5 实战案例总结

  1. Redis备份与恢复

13.1 Redis持久化与备份

13.1.1 RDB备份

13.1.2 AOF备份

13.2 备份策略

13.2.1 自动备份脚本

13.2.2 远程备份

13.2.3 备份策略建议

13.3 数据恢复方案

13.3.1 使用RDB文件恢复

13.3.2 使用AOF文件恢复

13.3.3 AOF文件修复

13.4 灾难恢复策略

13.4.1 主从复制恢复

13.4.2 使用Sentinel自动故障转移

13.4.3 Redis Cluster自动分片迁移

13.5 备份监控与验证

13.5.1 备份监控脚本

13.5.2 备份验证

13.6 迁移与升级方案

13.6.1 版本升级

13.6.2 实例迁移

13.6.3 在线迁移工具

13.7 备份与恢复最佳实践

  1. 总结

14.1 Redis核心价值总结

14.2 主要章节回顾

14.3 Redis最佳实践汇总

14.3.1 设计与开发最佳实践

14.4 扩展学习资源

14.5 结语

1. Redis 基础

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

1.1 Redis 简介

Redis(Remote Dictionary Server)是一个高性能的键值对数据库,具有以下特点:

  • 速度快:所有数据存储在内存中,操作速度非常快
  • 持久化:支持RDB和AOF两种持久化方式
  • 多种数据类型:支持字符串、哈希、列表、集合、有序集合等
  • 原子性操作:保证命令的原子性执行
  • 支持事务:可以批量执行命令
  • 支持发布订阅:可以实现消息的发布与订阅
  • 主从复制:支持主从复制和高可用配置
  • 支持Lua脚本:可以执行Lua脚本实现复杂逻辑

相比传统关系型数据库,Redis具有更高的性能和更灵活的数据结构,适用于缓存、会话存储、消息队列、排行榜等场景。

1.2 Redis与传统数据库的比较

特性 Redis 传统关系型数据库(如MySQL)
数据存储方式 内存为主,可持久化到磁盘 磁盘存储
数据结构 多种数据结构 表结构
事务 支持简单事务 完整ACID事务
性能 极高(10万QPS) 中等(1万QPS)
操作复杂度 简单 复杂
容量 受内存限制 受磁盘限制
扩展性 主从、集群 主从、分库分表
应用场景 缓存、计数器、排行榜、会话存储 结构化数据存储、复杂查询

1.3 Redis主要应用场景

  1. 缓存:减轻数据库压力,提高响应速度
  2. 会话存储:存储用户会话信息,支持分布式系统
  3. 计数器和限速器:高性能计数和速率限制
  4. 排行榜:利用有序集合实现实时排行榜
  5. 消息队列:轻量级消息中间件
  6. 分布式锁:实现分布式系统的同步机制
  7. 实时分析:实时数据统计和分析
  8. 地理位置应用:支持地理位置数据的存储和查询
  9. 社交网络Feed流:用户时间线功能

1.4 Redis安装和配置

1.4.1 Linux安装Redis
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install redis-server

# CentOS/RHEL
sudo yum install epel-release
sudo yum install redis

# 启动Redis服务
sudo systemctl start redis
sudo systemctl enable redis
1.4.2 Docker安装Redis
# 拉取Redis镜像
docker pull redis

# 运行Redis容器
docker run --name myredis -d -p 6379:6379 redis

# 使用自定义配置
docker run --name myredis -v /path/to/redis.conf:/usr/local/etc/redis/redis.conf -d -p 6379:6379 redis redis-server /usr/local/etc/redis/redis.conf
1.4.3 基本配置参数

Redis配置文件(redis.conf)中的关键配置:

# 网络配置
bind 127.0.0.1
port 6379
protected-mode yes

# 通用配置
daemonize yes
pidfile /var/run/redis/redis-server.pid
loglevel notice
logfile /var/log/redis/redis-server.log

# 内存管理
maxmemory 2gb
maxmemory-policy allkeys-lru

# 持久化配置
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
dir /var/lib/redis

# AOF配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec

1.5 Redis基本操作

1.5.1 连接Redis
# 本地连接
redis-cli

# 远程连接
redis-cli -h 192.168.1.100 -p 6379 -a password

# 安全连接(带密码)
redis-cli -a password
1.5.2 基本命令示例
# 设置和获取键值
SET name "John"
GET name

# 删除键
DEL name

# 检查键是否存在
EXISTS name

# 设置过期时间(秒)
SET token "12345" EX 3600

# 获取所有键
KEYS *

# 获取键类型
TYPE name

# 清空当前数据库
FLUSHDB

# 清空所有数据库
FLUSHALL

2. Redis 与 Spring Boot 集成

Spring Boot提供了Redis的自动配置功能,可以轻松地将Redis集成到应用程序中。

2.1 Maven依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>


<dependency>
    <groupId>org.apache.commonsgroupId>
    <artifactId>commons-pool2artifactId>
dependency>

2.2 基本配置

application.propertiesapplication.yml中配置Redis连接信息:

spring:
  redis:
    host: localhost
    port: 6379
    password: # Redis服务器密码,默认为空
    database: 0 # Redis数据库索引,默认为0
    timeout: 3000 # 连接超时时间
    lettuce:
      pool:
        max-active: 8 # 连接池最大连接数
        max-wait: -1ms # 连接池最大阻塞等待时间,负值表示没有限制
        max-idle: 8 # 连接池中的最大空闲连接
        min-idle: 0 # 连接池中的最小空闲连接

2.3 Redis Template 配置

创建RedisTemplateStringRedisTemplate的配置类:

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson2JsonRedisSerializer序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = 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);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        
        // 设置key和value的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(jackson2JsonRedisSerializer);
        
        // 设置hash的key和value的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

2.4 基本操作示例

2.4.1 使用RedisTemplate
@Service
public class RedisService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 存储键值对
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * 存储键值对并设置过期时间
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }
    
    /**
     * 获取值
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    
    /**
     * 删除键
     */
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }
    
    /**
     * 检查键是否存在
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }
    
    /**
     * 设置过期时间
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }
}
2.4.2 使用StringRedisTemplate
@Service
public class StringRedisService {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    /**
     * 存储字符串键值对
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * 获取字符串值
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }
    
    /**
     * 追加字符串
     */
    public Integer append(String key, String value) {
        return stringRedisTemplate.opsForValue().append(key, value);
    }
    
    /**
     * 递增操作
     */
    public Long increment(String key, long delta) {
        return stringRedisTemplate.opsForValue().increment(key, delta);
    }
}

2.5 操作不同数据类型

2.5.1 操作List
@Service
public class RedisListService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 从左侧添加元素
     */
    public Long leftPush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }
    
    /**
     * 从右侧添加元素
     */
    public Long rightPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }
    
    /**
     * 从左侧弹出元素
     */
    public Object leftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }
    
    /**
     * 从右侧弹出元素
     */
    public Object rightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }
    
    /**
     * 获取列表长度
     */
    public Long size(String key) {
        return redisTemplate.opsForList().size(key);
    }
    
    /**
     * 获取指定范围的元素
     */
    public List<Object> range(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
}
2.5.2 操作Hash
@Service
public class RedisHashService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 存储单个哈希字段
     */
    public void put(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }
    
    /**
     * 存储多个哈希字段
     */
    public void putAll(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }
    
    /**
     * 获取哈希字段值
     */
    public Object get(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }
    
    /**
     * 获取所有哈希字段和值
     */
    public Map<Object, Object> entries(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    
    /**
     * 删除一个或多个哈希字段
     */
    public Long delete(String key, Object... hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }
    
    /**
     * 判断哈希字段是否存在
     */
    public Boolean hasKey(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }
    
    /**
     * 哈希字段值递增
     */
    public Long increment(String key, String hashKey, long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, delta);
    }
}
2.5.3 操作Set
@Service
public class RedisSetService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 添加元素到集合
     */
    public Long add(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }
    
    /**
     * 获取集合中的所有元素
     */
    public Set<Object> members(String key) {
        return redisTemplate.opsForSet().members(key);
    }
    
    /**
     * 检查元素是否在集合中
     */
    public Boolean isMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }
    
    /**
     * 获取集合大小
     */
    public Long size(String key) {
        return redisTemplate.opsForSet().size(key);
    }
    
    /**
     * 移除集合中的元素
     */
    public Long remove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }
    
    /**
     * 随机获取集合中的元素
     */
    public Object randomMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }
    
    /**
     * 计算集合的交集
     */
    public Set<Object> intersect(String key, String otherKey) {
        return redisTemplate.opsForSet().intersect(key, otherKey);
    }
    
    /**
     * 计算集合的并集
     */
    public Set<Object> union(String key, String otherKey) {
        return redisTemplate.opsForSet().union(key, otherKey);
    }
    
    /**
     * 计算集合的差集
     */
    public Set<Object> difference(String key, String otherKey) {
        return redisTemplate.opsForSet().difference(key, otherKey);
    }
}
2.5.4 操作Sorted Set
@Service
public class RedisSortedSetService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 添加元素到有序集合
     */
    public Boolean add(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }
    
    /**
     * 从有序集合中移除元素
     */
    public Long remove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }
    
    /**
     * 增加元素的分数
     */
    public Double incrementScore(String key, Object value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }
    
    /**
     * 获取元素的分数
     */
    public Double score(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }
    
    /**
     * 获取元素的排名(从小到大)
     */
    public Long rank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }
    
    /**
     * 获取元素的排名(从大到小)
     */
    public Long reverseRank(String key, Object value) {
        return redisTemplate.opsForZSet().reverseRank(key, value);
    }
    
    /**
     * 获取指定范围的元素(从小到大)
     */
    public Set<Object> range(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }
    
    /**
     * 获取指定范围的元素及其分数(从小到大)
     */
    public Set<ZSetOperations.TypedTuple<Object>> rangeWithScores(String key, long start, long end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }
    
    /**
     * 获取指定范围的元素(从大到小)
     */
    public Set<Object> reverseRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }
    
    /**
     * 获取指定分数范围的元素
     */
    public Set<Object> rangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }
    
    /**
     * 获取有序集合大小
     */
    public Long size(String key) {
        return redisTemplate.opsForZSet().size(key);
    }
}

3. Redis 数据类型和操作

Redis支持多种数据类型,每种类型都有特定的操作命令和应用场景。本章将详细介绍Redis的五种基本数据类型以及其他扩展数据类型。

3.1 字符串(String)

字符串是Redis中最基本的数据类型,可以存储文本、整数或二进制数据。

3.1.1 基本命令
# 设置值
SET key value [EX seconds] [PX milliseconds] [NX|XX]

# 获取值
GET key

# 设置并返回旧值
GETSET key value

# 删除键
DEL key

# 同时设置多个键值对
MSET key1 value1 key2 value2 ...

# 同时获取多个键的值
MGET key1 key2 ...

# 设置过期时间(秒)
EXPIRE key seconds

# 自增1
INCR key

# 增加指定整数
INCRBY key increment

# 自减1
DECR key

# 减少指定整数
DECRBY key decrement

# 向字符串追加内容
APPEND key value

# 获取字符串长度
STRLEN key

# 截取字符串
GETRANGE key start end

# 替换字符串
SETRANGE key offset value
3.1.2 应用场景
  1. 缓存:存储热点数据,如用户信息、商品信息等
  2. 计数器:利用INCR和INCRBY命令实现高性能计数器
  3. 限流器:结合过期时间实现简单的限流功能
  4. 分布式锁:利用SET的NX选项实现简单的分布式锁
  5. 会话存储:存储用户会话信息
3.1.3 Spring Boot示例
@Service
public class StringOperationService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 设置字符串值
     */
    public void setValue(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * 设置带过期时间的值
     */
    public void setValueWithExpiration(String key, String value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }
    
    /**
     * 获取字符串值
     */
    public String getValue(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    
    /**
     * 字符串自增
     */
    public Long increment(String key) {
        return redisTemplate.opsForValue().increment(key);
    }
    
    /**
     * 字符串自增指定数值
     */
    public Long incrementBy(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }
    
    /**
     * 同时获取多个键值
     */
    public List<String> multiGet(List<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }
}

3.2 哈希(Hash)

哈希是一个string类型的field和value的映射表,特别适合用于存储对象。

3.2.1 基本命令
# 设置单个哈希字段
HSET key field value

# 获取单个哈希字段
HGET key field

# 检查字段是否存在
HEXISTS key field

# 删除一个或多个字段
HDEL key field [field ...]

# 获取所有字段和值
HGETALL key

# 获取所有字段名
HKEYS key

# 获取所有字段值
HVALS key

# 获取字段数量
HLEN key

# 设置多个字段值
HMSET key field1 value1 field2 value2 ...

# 获取多个字段值
HMGET key field1 field2 ...

# 字段值自增
HINCRBY key field increment

# 字段值浮点数自增
HINCRBYFLOAT key field increment
3.2.2 应用场景
  1. 存储对象:存储用户信息、商品信息等对象数据
  2. 计数统计:利用HINCRBY实现多维度计数
  3. 配置中心:存储应用配置信息
  4. 购物车:存储用户购物车商品信息
  5. 用户属性:管理用户的多个属性
3.2.3 Spring Boot示例
@Service
public class HashOperationService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 设置哈希字段
     */
    public void setHashField(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }
    
    /**
     * 获取哈希字段
     */
    public Object getHashField(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }
    
    /**
     * 获取哈希所有字段和值
     */
    public Map<Object, Object> getEntries(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    
    /**
     * 删除哈希字段
     */
    public Long deleteHashField(String key, Object... fields) {
        return redisTemplate.opsForHash().delete(key, fields);
    }
    
    /**
     * 哈希字段自增
     */
    public Long increment(String key, String field, long delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }
    
    /**
     * 检查字段是否存在
     */
    public Boolean hasKey(String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }
}

3.3 列表(List)

列表是简单的字符串列表,按照插入顺序排序,支持从两端操作元素。

3.3.1 基本命令
# 从左端插入元素
LPUSH key value [value ...]

# 从右端插入元素
RPUSH key value [value ...]

# 从左端弹出元素
LPOP key

# 从右端弹出元素
RPOP key

# 获取列表范围
LRANGE key start stop

# 移除元素
LREM key count value

# 通过索引获取元素
LINDEX key index

# 获取列表长度
LLEN key

# 在元素前/后插入元素
LINSERT key BEFORE|AFTER pivot value

# 设置指定索引的值
LSET key index value

# 保留指定范围的元素
LTRIM key start stop

# 阻塞式弹出元素
BLPOP key [key ...] timeout
BRPOP key [key ...] timeout
3.3.2 应用场景
  1. 消息队列:使用LPUSH+RPOP或RPUSH+LPOP实现简单的消息队列
  2. 最新动态:存储用户最新动态、消息列表
  3. 任务队列:存储待处理任务列表
  4. 回复列表:存储博客文章的最新评论
  5. 用户操作记录:记录用户最近的操作历史
3.3.3 Spring Boot示例
@Service
public class ListOperationService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 从左侧添加元素
     */
    public Long leftPush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }
    
    /**
     * 从右侧添加元素
     */
    public Long rightPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }
    
    /**
     * 从左侧弹出元素
     */
    public Object leftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }
    
    /**
     * 从右侧弹出元素
     */
    public Object rightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }
    
    /**
     * 获取指定范围的元素
     */
    public List<Object> range(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
    
    /**
     * 获取列表长度
     */
    public Long size(String key) {
        return redisTemplate.opsForList().size(key);
    }
    
    /**
     * 移除元素
     */
    public Long remove(String key, long count, Object value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }
}

3.4 集合(Set)

集合是无序的字符串集合,不允许有重复成员。

3.4.1 基本命令
# 添加一个或多个元素
SADD key member [member ...]

# 获取所有成员
SMEMBERS key

# 判断元素是否在集合中
SISMEMBER key member

# 获取集合大小
SCARD key

# 移除一个或多个成员
SREM key member [member ...]

# 随机获取成员
SRANDMEMBER key [count]

# 弹出随机成员
SPOP key [count]

# 集合运算 - 交集
SINTER key [key ...]

# 集合运算 - 并集
SUNION key [key ...]

# 集合运算 - 差集
SDIFF key [key ...]

# 将交集存储到新集合
SINTERSTORE destination key [key ...]

# 将并集存储到新集合
SUNIONSTORE destination key [key ...]

# 将差集存储到新集合
SDIFFSTORE destination key [key ...]
3.4.2 应用场景
  1. 标签系统:为用户或内容添加标签
  2. 唯一计数:如网站独立访客统计
  3. 共同好友:利用交集计算共同好友
  4. 推荐系统:基于用户兴趣交集推荐内容
  5. 黑白名单:存储IP黑白名单
3.4.3 Spring Boot示例
@Service
public class SetOperationService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 添加成员
     */
    public Long add(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }
    
    /**
     * 获取所有成员
     */
    public Set<Object> members(String key) {
        return redisTemplate.opsForSet().members(key);
    }
    
    /**
     * 判断元素是否在集合中
     */
    public Boolean isMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }
    
    /**
     * 获取集合大小
     */
    public Long size(String key) {
        return redisTemplate.opsForSet().size(key);
    }
    
    /**
     * 移除成员
     */
    public Long remove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }
    
    /**
     * 随机获取成员
     */
    public Object randomMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }
    
    /**
     * 求交集
     */
    public Set<Object> intersect(String key, String otherKey) {
        return redisTemplate.opsForSet().intersect(key, otherKey);
    }
    
    /**
     * 求并集
     */
    public Set<Object> union(String key, String otherKey) {
        return redisTemplate.opsForSet().union(key, otherKey);
    }
    
    /**
     * 求差集
     */
    public Set<Object> difference(String key, String otherKey) {
        return redisTemplate.opsForSet().difference(key, otherKey);
    }
}

3.5 有序集合(Sorted Set)

有序集合与集合类似,但每个成员都关联一个分数,根据分数排序。

3.5.1 基本命令
# 添加成员和分数
ZADD key score member [score member ...]

# 获取指定排名范围的成员
ZRANGE key start stop [WITHSCORES]

# 获取指定分数范围的成员
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

# 获取成员分数
ZSCORE key member

# 获取成员排名(从低到高)
ZRANK key member

# 获取成员排名(从高到低)
ZREVRANK key member

# 移除成员
ZREM key member [member ...]

# 增加成员分数
ZINCRBY key increment member

# 计算有序集合的交集
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

# 计算有序集合的并集
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

# 获取集合大小
ZCARD key

# 获取指定分数范围的成员数量
ZCOUNT key min max

# 移除指定排名范围的成员
ZREMRANGEBYRANK key start stop

# 移除指定分数范围的成员
ZREMRANGEBYSCORE key min max
3.5.2 应用场景
  1. 排行榜:如游戏积分排行、热门文章排行
  2. 权重队列:根据优先级处理任务
  3. 延迟队列:使用时间戳作为分数实现延迟任务
  4. 带权重的搜索结果:存储搜索结果并按相关性排序
  5. 自动补全:实现搜索自动补全功能
3.5.3 Spring Boot示例
@Service
public class ZSetOperationService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 添加成员和分数
     */
    public Boolean add(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }
    
    /**
     * 获取指定排名范围的成员
     */
    public Set<Object> range(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }
    
    /**
     * 获取成员排名(从低到高)
     */
    public Long rank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }
    
    /**
     * 获取成员排名(从高到低)
     */
    public Long reverseRank(String key, Object value) {
        return redisTemplate.opsForZSet().reverseRank(key, value);
    }
    
    /**
     * 获取成员分数
     */
    public Double score(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }
    
    /**
     * 增加成员分数
     */
    public Double incrementScore(String key, Object value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }
    
    /**
     * 移除成员
     */
    public Long remove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }
    
    /**
     * 获取指定分数范围的成员
     */
    public Set<Object> rangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }
    
    /**
     * 获取有序集合大小
     */
    public Long size(String key) {
        return redisTemplate.opsForZSet().size(key);
    }
}

4. Redis 高级特性

除了基本的数据类型和操作外,Redis还提供了许多高级特性,可以满足更复杂的应用场景。

4.1 事务

Redis事务允许执行一组命令,确保他们被顺序执行且不被其他客户端命令打断。

4.1.1 基本命令
# 开始事务
MULTI

# 在事务中添加命令
(命令会被入队但不执行)

# 执行事务
EXEC

# 取消事务
DISCARD

# 监控键,如果键在EXEC前被修改,事务将被取消
WATCH key [key ...]

# 取消所有监控
UNWATCH
4.1.2 特点与限制
  • 非原子性:如果其中一个命令执行失败,其他命令仍会执行
  • 无隔离级别:事务未提交时,其他客户端可以看到修改
  • 无回滚机制:命令出错不会回滚已执行的命令
4.1.3 Spring Boot示例
@Service
public class RedisTransactionExample {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 使用Redis事务转账示例
     */
    public boolean transfer(String fromAccount, String toAccount, double amount) {
        String fromKey = "account:" + fromAccount;
        String toKey = "account:" + toAccount;
        
        // 监控账户余额键
        redisTemplate.watch(fromKey);
        
        // 获取转出账户余额
        String balanceStr = redisTemplate.opsForValue().get(fromKey);
        
        if (balanceStr == null) {
            // 账户不存在
            redisTemplate.unwatch();
            return false;
        }
        
        double balance = Double.parseDouble(balanceStr);
        
        if (balance < amount) {
            // 余额不足
            redisTemplate.unwatch();
            return false;
        }
        
        // 开始事务
        redisTemplate.multi();
        
        // 减少转出账户余额
        redisTemplate.opsForValue().set(fromKey, String.valueOf(balance - amount));
        
        // 获取转入账户余额并增加
        String toBalanceStr = redisTemplate.opsForValue().get(toKey);
        double toBalance = toBalanceStr != null ? Double.parseDouble(toBalanceStr) : 0;
        redisTemplate.opsForValue().set(toKey, String.valueOf(toBalance + amount));
        
        // 执行事务
        List<Object> results = redisTemplate.exec();
        
        // 如果results为null,说明事务执行失败(可能是键被其他客户端修改)
        return results != null;
    }
    
    /**
     * 使用SessionCallback实现事务
     */
    public boolean transferWithSessionCallback(String fromAccount, String toAccount, double amount) {
        String fromKey = "account:" + fromAccount;
        String toKey = "account:" + toAccount;
        
        return redisTemplate.execute(new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                operations.watch((K) fromKey);
                
                String balanceStr = (String) operations.opsForValue().get(fromKey);
                
                if (balanceStr == null) {
                    operations.unwatch();
                    return false;
                }
                
                double balance = Double.parseDouble(balanceStr);
                
                if (balance < amount) {
                    operations.unwatch();
                    return false;
                }
                
                operations.multi();
                
                operations.opsForValue().set((K) fromKey, (V) String.valueOf(balance - amount));
                
                String toBalanceStr = (String) operations.opsForValue().get(toKey);
                double toBalance = toBalanceStr != null ? Double.parseDouble(toBalanceStr) : 0;
                operations.opsForValue().set((K) toKey, (V) String.valueOf(toBalance + amount));
                
                // 执行事务
                List<Object> results = operations.exec();
                
                return results != null && !results.isEmpty();
            }
        });
    }
}

4.2 发布订阅

Redis提供了发布订阅功能,允许消息发布者将消息发送到指定的频道,订阅该频道的所有客户端都可以接收到消息。

4.2.1 基本命令
# 发布消息
PUBLISH channel message

# 订阅频道
SUBSCRIBE channel [channel ...]

# 取消订阅
UNSUBSCRIBE [channel [channel ...]]

# 按模式订阅频道
PSUBSCRIBE pattern [pattern ...]

# 取消按模式订阅
PUNSUBSCRIBE [pattern [pattern ...]]

# 查看订阅数量
PUBSUB NUMSUB [channel ...]

# 查看匹配模式的频道
PUBSUB CHANNELS [pattern]
4.2.2 应用场景
  1. 实时消息通知:如聊天应用、系统通知
  2. 实时数据更新:更新缓存、实时统计
  3. 系统解耦:服务间通信
  4. 任务调度:分发任务到处理节点
4.2.3 Spring Boot示例
@Configuration
public class RedisMessageConfig {
    
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(
            RedisConnectionFactory connectionFactory,
            MessageListenerAdapter listenerAdapter) {
        
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 可以订阅多个主题
        container.addMessageListener(listenerAdapter, new PatternTopic("chat:*"));
        return container;
    }
    
    @Bean
    public MessageListenerAdapter listenerAdapter(RedisMessageSubscriber subscriber) {
        // 指定订阅者和处理消息的方法
        return new MessageListenerAdapter(subscriber, "onMessage");
    }
    
    @Bean
    public RedisMessagePublisher redisMessagePublisher(
            RedisConnectionFactory connectionFactory,
            RedisTemplate<String, Object> redisTemplate) {
        
        return new RedisMessagePublisher(connectionFactory, redisTemplate);
    }
}

@Component
public class RedisMessageSubscriber {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisMessageSubscriber.class);
    
    public void onMessage(String message, String pattern) {
        LOGGER.info("收到消息: {} 从频道: {}", message, pattern);
        
        // 处理接收到的消息
        // 可以将消息序列化为对象,进行后续处理
        ChatMessage chatMessage = JSON.parseObject(message, ChatMessage.class);
        
        // 处理消息逻辑...
    }
    
    @Data
    public static class ChatMessage {
        private String from;
        private String to;
        private String content;
        private Date timestamp;
    }
}

@Component
public class RedisMessagePublisher {
    
    private final RedisConnectionFactory connectionFactory;
    private final RedisTemplate<String, Object> redisTemplate;
    
    public RedisMessagePublisher(
            RedisConnectionFactory connectionFactory,
            RedisTemplate<String, Object> redisTemplate) {
        this.connectionFactory = connectionFactory;
        this.redisTemplate = redisTemplate;
    }
    
    public void publish(String channel, Object message) {
        redisTemplate.convertAndSend(channel, message);
    }
}

@Service
public class ChatService {
    
    @Autowired
    private RedisMessagePublisher publisher;
    
    public void sendMessage(String roomId, String from, String content) {
        RedisMessageSubscriber.ChatMessage message = new RedisMessageSubscriber.ChatMessage();
        message.setFrom(from);
        message.setTo(roomId);
        message.setContent(content);
        message.setTimestamp(new Date());
        
        // 发布消息到指定聊天室频道
        publisher.publish("chat:room:" + roomId, JSON.toJSONString(message));
    }
    
    public void sendPrivateMessage(String from, String to, String content) {
        RedisMessageSubscriber.ChatMessage message = new RedisMessageSubscriber.ChatMessage();
        message.setFrom(from);
        message.setTo(to);
        message.setContent(content);
        message.setTimestamp(new Date());
        
        // 发布消息到用户私聊频道
        publisher.publish("chat:private:" + to, JSON.toJSONString(message));
    }
}

4.3 Lua脚本

Redis支持使用Lua脚本执行原子操作,可以避免多次网络往返并确保操作的原子性。

4.3.1 基本命令
# 执行Lua脚本
EVAL script numkeys key [key ...] arg [arg ...]

# 使用SHA执行已缓存的脚本
EVALSHA sha1 numkeys key [key ...] arg [arg ...]

# 加载脚本到缓存
SCRIPT LOAD script

# 检查脚本是否已缓存
SCRIPT EXISTS sha1 [sha1 ...]

# 清除脚本缓存
SCRIPT FLUSH

# 终止正在运行的脚本
SCRIPT KILL
4.3.2 脚本示例

限流器脚本:

-- 限流脚本
-- KEYS[1]: 限流器key
-- ARGV[1]: 最大请求数
-- ARGV[2]: 时间窗口(秒)
local key = KEYS[1]
local max_requests = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current_time = redis.call('TIME')[1]
local window_start = current_time - window

-- 移除时间窗口之前的记录
redis.call('ZREMRANGEBYSCORE', key, 0, window_start)

-- 获取当前窗口内的请求数
local count = redis.call('ZCARD', key)

-- 如果请求数小于限制,允许请求
if count < max_requests then
    -- 添加当前请求记录
    redis.call('ZADD', key, current_time, current_time .. ":" .. math.random())
    -- 设置过期时间,防止key永久存在
    redis.call('EXPIRE', key, window)
    return 1
else
    return 0
end

分布式锁脚本:

-- 分布式锁获取脚本
-- KEYS[1]: 锁的key
-- ARGV[1]: 请求ID (随机字符串)
-- ARGV[2]: 过期时间(秒)
local key = KEYS[1]
local requestId = ARGV[1]
local expireTime = ARGV[2]

if redis.call('EXISTS', key) == 0 then
    redis.call('SET', key, requestId)
    redis.call('EXPIRE', key, expireTime)
    return 1
else
    return 0
end

-- 分布式锁释放脚本
-- KEYS[1]: 锁的key
-- ARGV[1]: 请求ID
local key = KEYS[1]
local requestId = ARGV[1]

if redis.call('GET', key) == requestId then
    return redis.call('DEL', key)
else
    return 0
end
4.3.3 Spring Boot示例
@Service
public class RedisLuaScriptExample {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 使用限流器脚本
     */
    public boolean isAllowed(String key, int maxRequests, int windowSeconds) {
        String script = 
                "local key = KEYS[1] " +
                "local max_requests = tonumber(ARGV[1]) " +
                "local window = tonumber(ARGV[2]) " +
                "local current_time = redis.call('TIME')[1] " +
                "local window_start = current_time - window " +
                
                "redis.call('ZREMRANGEBYSCORE', key, 0, window_start) " +
                
                "local count = redis.call('ZCARD', key) " +
                
                "if count < max_requests then " +
                "    redis.call('ZADD', key, current_time, current_time .. ':' .. math.random()) " +
                "    redis.call('EXPIRE', key, window) " +
                "    return 1 " +
                "else " +
                "    return 0 " +
                "end";
        
        Long result = redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Collections.singletonList(key),
                String.valueOf(maxRequests), String.valueOf(windowSeconds)
        );
        
        return Long.valueOf(1).equals(result);
    }
    
    /**
     * 使用Lua脚本实现分布式锁
     */
    public boolean acquireLock(String lockKey, String requestId, int expireSeconds) {
        String script = 
                "if redis.call('EXISTS', KEYS[1]) == 0 then " +
                "    redis.call('SET', KEYS[1], ARGV[1]) " +
                "    redis.call('EXPIRE', KEYS[1], ARGV[2]) " +
                "    return 1 " +
                "else " +
                "    return 0 " +
                "end";
        
        Long result = redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Collections.singletonList(lockKey),
                requestId, String.valueOf(expireSeconds)
        );
        
        return Long.valueOf(1).equals(result);
    }
    
    /**
     * 使用Lua脚本释放分布式锁
     */
    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";
        
        Long result = redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Collections.singletonList(lockKey),
                requestId
        );
        
        return Long.valueOf(1).equals(result);
    }
    
    /**
     * 使用Lua脚本实现计数器并批量递增
     */
    public Map<String, Long> batchIncrement(List<String> keys, long increment) {
        String script = 
                "local results = {} " +
                "for i, key in ipairs(KEYS) do " +
                "    local current = redis.call('get', key) " +
                "    local value " +
                "    if current then " +
                "        value = tonumber(current) + tonumber(ARGV[1]) " +
                "    else " +
                "        value = tonumber(ARGV[1]) " +
                "    end " +
                "    redis.call('set', key, value) " +
                "    results[i] = value " +
                "end " +
                "return results";
        
        List<Long> results = redisTemplate.execute(
                new DefaultRedisScript<>(script, List.class),
                keys,
                String.valueOf(increment)
        );
        
        Map<String, Long> resultMap = new HashMap<>();
        
        if (results != null) {
            for (int i = 0; i < results.size(); i++) {
                resultMap.put(keys.get(i), results.get(i));
            }
        }
        
        return resultMap;
    }
    
    /**
     * 使用Lua脚本实现令牌桶限流器
     */
    public boolean acquireTokens(String key, int tokens, int burstCapacity, double tokensPerSecond) {
        String script = 
                "local key = KEYS[1] " +
                "local requested = tonumber(ARGV[1]) " +
                "local capacity = tonumber(ARGV[2]) " +
                "local rate = tonumber(ARGV[3]) " +
                "local now = redis.call('time') " +
                "local timestamp = tonumber(now[1]) + (tonumber(now[2]) / 1000000) " +
                
                "local lastTokens = redis.call('hget', key, 'tokens') " +
                "local lastTimestamp = redis.call('hget', key, 'timestamp') " +
                
                "local currentTokens " +
                "if lastTokens and lastTimestamp then " +
                "    currentTokens = math.min(capacity, tonumber(lastTokens) + ((timestamp - tonumber(lastTimestamp)) * rate)) " +
                "else " +
                "    currentTokens = capacity " +
                "end " +
                
                "local allowed = requested <= currentTokens " +
                "local newTokens = currentTokens " +
                
                "if allowed then " +
                "    newTokens = currentTokens - requested " +
                "end " +
                
                "redis.call('hmset', key, 'tokens', newTokens, 'timestamp', timestamp) " +
                "redis.call('expire', key, 60) " +
                
                "return allowed";
        
        Boolean result = redisTemplate.execute(
                new DefaultRedisScript<>(script, Boolean.class),
                Collections.singletonList(key),
                String.valueOf(tokens), String.valueOf(burstCapacity), String.valueOf(tokensPerSecond)
        );
        
        return Boolean.TRUE.equals(result);
    }
}

4.4 持久化

Redis提供了两种持久化方式:RDB和AOF,以确保数据不会因服务器重启而丢失。

4.4.1 RDB(Redis Database)

RDB是按指定的时间间隔将数据快照保存到磁盘。

配置方式:

# 900秒内至少1个key变更,则触发保存
save 900 1
# 300秒内至少10个key变更,则触发保存
save 300 10
# 60秒内至少10000个key变更,则触发保存
save 60 10000

# 快照文件名
dbfilename dump.rdb

# 快照文件存储目录
dir /var/lib/redis

# 当RDB持久化出错时,是否继续提供服务
stop-writes-on-bgsave-error yes

# 是否压缩RDB文件
rdbcompression yes

# 是否校验RDB文件
rdbchecksum yes

优点:

  • 性能影响小:RDB在后台进行,对服务性能影响较小
  • 快速恢复:恢复大数据集比AOF快
  • 适合备份:生成一个文件,便于备份和迁移

缺点:

  • 可能丢失数据:两次快照之间的数据可能丢失
  • fork子进程:创建快照时需要fork子进程,占用内存
4.4.2 AOF(Append Only File)

AOF是记录所有写命令,通过重放这些命令来恢复数据。

配置方式:

# 开启AOF
appendonly yes

# AOF文件名
appendfilename "appendonly.aof"

# 同步策略:
# always: 每个命令都同步,最安全但最慢
# everysec: 每秒同步一次,平衡安全和性能
# no: 由操作系统决定何时同步,最快但最不安全
appendfsync everysec

# 当AOF文件大小超过上次重写时的大小的百分比时,触发重写
auto-aof-rewrite-percentage 100

# 触发重写的最小文件大小
auto-aof-rewrite-min-size 64mb

# 加载AOF时,忽略最后一条可能不完整的命令
aof-load-truncated yes

# AOF文件以RDB格式开头,提高加载速度(Redis 7.0+)
aof-use-rdb-preamble yes

优点:

  • 数据安全:可以配置每个命令或每秒同步,数据丢失风险小
  • 追加写入:AOF以追加方式写入,即使服务器崩溃也不会破坏文件
  • 自动重写:AOF文件过大时会自动重写,减小文件体积

缺点:

  • 文件体积较大:文件体积通常比RDB文件大
  • 性能影响:如果使用always同步策略,性能下降明显
  • 恢复较慢:通过AOF恢复数据库比RDB慢
4.4.3 混合持久化

Redis 4.0引入了混合持久化模式,结合了RDB和AOF的优点:

# 开启混合持久化(Redis 4.0+)
aof-use-rdb-preamble yes

这种模式下,AOF重写时会将已有数据以RDB格式写入,后续命令仍以AOF格式追加,既保证了恢复速度,又降低了数据丢失风险。

4.5 集群与高可用

Redis提供了多种方式实现集群和高可用配置。

4.5.1 主从复制

主从复制提供了数据的冗余备份,也是高可用的基础。

配置方式:

主节点配置:

# 主节点不需要特殊配置

从节点配置:

# 配置主节点IP和端口
replicaof 192.168.1.1 6379

# 如果主节点有密码
masterauth "master-password"

# 从节点是否可以接受写命令(默认只读)
replica-read-only yes

特点:

  • 数据备份:数据自动从主节点复制到从节点
  • 读写分离:主节点处理写请求,从节点处理读请求
  • 不提供自动故障转移:主节点故障需手动干预
4.5.2 Redis Sentinel

Sentinel提供高可用,监控主从节点,自动故障转移。

配置方式:

sentinel.conf:

# 监控的主节点,及判断其下线所需的sentinel数量
sentinel monitor mymaster 192.168.1.1 6379 2

# 主节点认证密码
sentinel auth-pass mymaster "master-password"

# 判断节点失效的时间(毫秒)
sentinel down-after-milliseconds mymaster 30000

# 故障转移超时时间(毫秒)
sentinel failover-timeout mymaster 180000

# 同时进行故障转移的slave数量
sentinel parallel-syncs mymaster 1

特点:

  • 监控:监控主从节点健康状态
  • 通知:通过API通知系统管理员或其他程序
  • 自动故障转移:主节点失效时自动选择从节点升级为主节点
  • 配置提供者:为客户端提供服务发现
4.5.3 Redis Cluster

Redis Cluster提供自动分片,在多节点间分布数据。

配置方式:

redis.conf:

# 开启集群模式
cluster-enabled yes

# 集群配置文件
cluster-config-file nodes-6379.conf

# 节点超时时间(毫秒)
cluster-node-timeout 15000

# 集群节点IP和端口
# 每个节点需要两个端口:普通端口和集群总线端口(普通端口+10000)
cluster-announce-ip 192.168.1.1
cluster-announce-port 6379
cluster-announce-bus-port 16379

特点:

  • 自动分片:数据自动分布在多个节点
  • 去中心化:无中央节点,节点间对等
  • 高可用:每个主节点可配置多个从节点
  • 自动故障转移:主节点故障时自动选择从节点升级
  • 分片重新平衡:节点加入或离开时自动重新分片
4.5.4 Spring Boot配置示例

单节点配置:

spring:
  redis:
    host: localhost
    port: 6379
    password: yourpassword
    database: 0

Sentinel配置:

spring:
  redis:
    sentinel:
      master: mymaster
      nodes:
        - 192.168.1.1:26379
        - 192.168.1.2:26379
        - 192.168.1.3:26379
    password: yourpassword
    database: 0

Cluster配置:

spring:
  redis:
    cluster:
      nodes:
        - 192.168.1.1:6379
        - 192.168.1.2:6379
        - 192.168.1.3:6379
        - 192.168.1.4:6379
        - 192.168.1.5:6379
        - 192.168.1.6:6379
      max-redirects: 3
    password: yourpassword

使用Redisson客户端:

@Configuration
public class RedissonConfig {
    
    @Bean
    public RedissonClient redissonClient(RedisProperties redisProperties) {
        Config config = new Config();
        config.useSingleServer()
              .setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort())
              .setPassword(redisProperties.getPassword())
              .setDatabase(redisProperties.getDatabase());
        return Redisson.create(config);
    }
}

4.6 Redis 6.0+新特性

Redis 6.0及以上版本引入了一系列重要的新特性,这些特性在安全性、性能和功能性上都有很大提升。

4.6.1 访问控制列表(ACL)

Redis 6.0引入了细粒度的权限控制机制,可以为不同用户指定访问权限。

命令示例:

# 创建新用户并设置密码
ACL SETUSER alice on >password123 ~cached:* +get +set +publish

# 查看用户列表
ACL LIST

# 切换当前连接的用户
AUTH alice password123

在Spring Boot中配置ACL用户:

spring:
  redis:
    username: alice
    password: password123
4.6.2 客户端缓存

Redis 6.0引入了客户端缓存功能,允许客户端在本地缓存数据,减少网络请求。

两种模式:

  1. 默认追踪模式:Redis服务器追踪客户端访问的键
  2. 广播模式:Redis服务器主动发送失效消息给客户端

在Spring Boot中配置客户端缓存:

@Configuration
public class RedisClientCacheConfig {
    
    @Bean
    public ClientResources clientResources() {
        return ClientResources.builder()
                .clientCacheConfig(ClientCacheConfig.builder()
                        .enabled(true)
                        .cacheSize(1000)
                        .build())
                .build();
    }
    
    @Bean
    public LettuceClientConfiguration lettuceClientConfiguration(ClientResources clientResources) {
        return LettuceClientConfiguration.builder()
                .clientResources(clientResources)
                .clientOptions(ClientOptions.builder()
                        .protocolVersion(ProtocolVersion.RESP3) // 必须使用RESP3协议
                        .build())
                .build();
    }
    
    @Bean
    public RedisConnectionFactory redisConnectionFactory(
            RedisProperties properties, LettuceClientConfiguration lettuceClientConfiguration) {
        
        LettuceConnectionFactory factory = new LettuceConnectionFactory(
                new RedisStandaloneConfiguration(properties.getHost(), properties.getPort()),
                lettuceClientConfiguration
        );
        
        if (properties.getPassword() != null) {
            factory.getStandaloneConfiguration().setPassword(properties.getPassword());
        }
        
        return factory;
    }
}
4.6.3 多线程I/O

Redis 6.0引入了多线程I/O处理,显著提高了网络I/O性能,但核心命令处理仍然是单线程的。

配置示例:

# redis.conf
io-threads 4           # 设置I/O线程数
io-threads-do-reads yes # I/O线程处理读操作

性能影响:

多线程I/O在高并发场景下可以提高吞吐量30%-50%,特别是对于大数据包的处理更有优势。

4.6.4 RESP3协议

Redis 6.0引入了新的RESP3协议,提供了更丰富的数据类型和更好的错误处理。

主要改进:

  • 新的数据类型:Map, Set, Attribute等
  • 可推送值:服务器可以主动向客户端推送数据
  • 改进的错误处理:更详细的错误信息

在Spring Boot中使用RESP3:

spring:
  redis:
    lettuce:
      client-options:
        protocol: resp3
4.6.5 SSL增强

Redis 6.0增强了SSL支持,使Redis通信更加安全。

配置SSL:

# redis.conf
port 0              # 禁用非SSL端口
tls-port 6379       # 启用SSL端口
tls-cert-file /path/to/server.crt
tls-key-file /path/to/server.key
tls-ca-cert-file /path/to/ca.crt

在Spring Boot中配置SSL连接:

spring:
  redis:
    ssl: true
@Bean
public RedisConnectionFactory redisConnectionFactory() {
    RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
    config.setHostName("your-redis-host");
    config.setPort(6379);
    
    // SSL配置
    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
            .useSsl().build();
    
    return new LettuceConnectionFactory(config, clientConfig);
}
4.6.6 有序集合(ZSET)增强功能

Redis 6.0为有序集合添加了多个新命令,增强了操作灵活性。

新增命令:

  • ZPOPMAX/ZPOPMIN: 弹出并返回得分最高/最低的成员
  • ZRANGESTORE: 将范围内的元素存储到新集合
  • ZRANDMEMBER: 随机获取成员

在Spring Boot中使用新命令:

@Service
public class ZSetEnhancedService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 随机获取排行榜上的玩家
    public Set<Object> getRandomPlayers(String leaderboardKey, long count) {
        return redisTemplate.opsForZSet().randomMembers(leaderboardKey, count);
    }
    
    // 弹出并返回最高分玩家
    public ZSetOperations.TypedTuple<Object> popTopPlayer(String leaderboardKey) {
        return redisTemplate.opsForZSet().popMax(leaderboardKey);
    }
    
    // 将前十名玩家存储到新的集合
    public Long storeTopTenPlayers(String sourceKey, String destKey) {
        return redisTemplate.opsForZSet().rangeStoreByScore(
                destKey, sourceKey, Range.unbounded(), Limit.limit().count(10)
        );
    }
}

这些新特性大大增强了Redis的功能和性能,建议在实际项目中根据需要进行合理配置和使用。

第5章 Redis缓存问题与解决方案

5.1 缓存穿透

5.1.1 问题描述

缓存穿透是指查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求数据库,如果有恶意请求故意查询不存在的数据,会导致数据库压力过大。

5.1.2 解决方案

1. 布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层数据库的查询压力。

@Configuration
public class BloomFilterConfig {
    @Bean
    public BloomFilter<String> bloomFilter() {
        // 预计数据量为100万,错误率为0.01
        return BloomFilter.create(
            Funnels.stringFunnel(Charset.forName("UTF-8")), 
            1000000, 
            0.01);
    }
}

@Service
public class ProductService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private BloomFilter<String> bloomFilter;
    
    @Autowired
    private ProductMapper productMapper;
    
    public Product getProductById(String id) {
        // 先判断id是否可能存在
        if (!bloomFilter.mightContain(id)) {
            return null;
        }
        
        // 查询缓存
        String productJson = redisTemplate.opsForValue().get("product:" + id);
        if (productJson != null) {
            return JSON.parseObject(productJson, Product.class);
        }
        
        // 查询数据库
        Product product = productMapper.selectById(id);
        if (product != null) {
            // 放入缓存
            redisTemplate.opsForValue().set("product:" + id, JSON.toJSONString(product), 1, TimeUnit.HOURS);
            // 添加到布隆过滤器
            bloomFilter.put(id);
        } else {
            // 可以设置一个空值到缓存,防止下次再查
            redisTemplate.opsForValue().set("product:" + id, "", 5, TimeUnit.MINUTES);
        }
        
        return product;
    }
}

2. 缓存空对象

当查询不存在的数据时,我们仍然将空值写入缓存,但设置较短的过期时间。

@Service
public class UserService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private UserMapper userMapper;
    
    public User getUserById(Long id) {
        String key = "user:" + id;
        
        // 查询缓存
        String userJson = redisTemplate.opsForValue().get(key);
        
        // 判断是否为空值的标记
        if ("".equals(userJson)) {
            return null;
        }
        
        if (userJson != null) {
            return JSON.parseObject(userJson, User.class);
        }
        
        // 查询数据库
        User user = userMapper.selectById(id);
        
        if (user != null) {
            // 写入缓存
            redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 1, TimeUnit.HOURS);
        } else {
            // 写入空值,短期过期
            redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
        }
        
        return user;
    }
}

5.2 缓存击穿

5.2.1 问题描述

缓存击穿是指热点key在过期的一瞬间,同时有大量的请求打到了数据库上,导致数据库压力瞬间增大。

5.2.2 解决方案

1. 互斥锁

在缓存失效的时候,使用互斥锁来控制只有一个线程去查询数据库并更新缓存,其他线程等待。

@Service
public class HotProductService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private ProductMapper productMapper;
    
    public Product getHotProduct(Long id) {
        String key = "product:" + id;
        String lockKey = "lock:product:" + id;
        
        // 查询缓存
        String productJson = redisTemplate.opsForValue().get(key);
        if (productJson != null) {
            return JSON.parseObject(productJson, Product.class);
        }
        
        // 获取互斥锁
        boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        if (!locked) {
            // 获取锁失败,休眠一段时间后重试
            try {
                Thread.sleep(50);
                return getHotProduct(id);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        try {
            // 双重检查
            productJson = redisTemplate.opsForValue().get(key);
            if (productJson != null) {
                return JSON.parseObject(productJson, Product.class);
            }
            
            // 查询数据库
            Product product = productMapper.selectById(id);
            
            // 写入缓存
            if (product != null) {
                redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 1, TimeUnit.HOURS);
            } else {
                redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
            }
            
            return product;
        } finally {
            // 释放锁
            redisTemplate.delete(lockKey);
        }
    }
}

2. 提前更新

在key快要过期的时候,提前进行异步更新。

@Service
public class CacheRefreshService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private ProductMapper productMapper;
    
    private static final double REFRESH_THRESHOLD = 0.8;
    
    @Scheduled(fixedRate = 1000) // 每秒执行一次
    public void refreshHotKeys() {
        Set<String> hotKeys = redisTemplate.opsForSet().members("hot:keys");
        if (hotKeys == null || hotKeys.isEmpty()) {
            return;
        }
        
        for (String key : hotKeys) {
            Long expireTime = redisTemplate.getExpire(key, TimeUnit.SECONDS);
            
            // 如果过期时间小于阈值,则异步刷新
            if (expireTime != null && expireTime > 0 && expireTime < 60 * REFRESH_THRESHOLD) {
                asyncRefresh(key);
            }
        }
    }
    
    @Async
    public void asyncRefresh(String key) {
        // 从key中提取ID
        String idStr = key.substring(key.lastIndexOf(":") + 1);
        Long id = Long.valueOf(idStr);
        
        // 查询数据库
        Product product = productMapper.selectById(id);
        
        // 更新缓存
        if (product != null) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 1, TimeUnit.HOURS);
        }
    }
}

5.3 缓存雪崩

5.3.1 问题描述

缓存雪崩是指在某一个时间段内,大量的缓存集中过期或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

5.3.2 解决方案

1. 过期时间随机化

给缓存的过期时间加上一个随机值,避免集中过期。

@Service
public class RandomExpiryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private ProductMapper productMapper;
    
    private final Random random = new Random();
    
    public Product getProduct(Long id) {
        String key = "product:" + id;
        
        // 查询缓存
        String productJson = redisTemplate.opsForValue().get(key);
        if (productJson != null) {
            return JSON.parseObject(productJson, Product.class);
        }
        
        // 查询数据库
        Product product = productMapper.selectById(id);
        
        if (product != null) {
            // 过期时间3600-4500秒之间随机
            int expireTime = 3600 + random.nextInt(900);
            redisTemplate.opsForValue().set(key, JSON.toJSONString(product), expireTime, TimeUnit.SECONDS);
        }
        
        return product;
    }
}

2. 缓存高可用

搭建Redis集群,即使单个节点故障,整个缓存系统仍然可用。

3. 限流降级

在缓存失效后,通过限流或熔断机制降低数据库的压力。

@Service
public class RateLimitService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private ProductMapper productMapper;
    
    private final RateLimiter rateLimiter = RateLimiter.create(100.0); // 限制QPS为100
    
    public Product getProductWithRateLimit(Long id) {
        String key = "product:" + id;
        
        // 查询缓存
        String productJson = redisTemplate.opsForValue().get(key);
        if (productJson != null) {
            return JSON.parseObject(productJson, Product.class);
        }
        
        // 获取令牌,等待最多100ms
        boolean acquired = rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS);
        if (!acquired) {
            // 获取令牌失败,返回降级数据或抛出异常
            throw new ServiceException("系统繁忙,请稍后再试");
        }
        
        // 查询数据库
        Product product = productMapper.selectById(id);
        
        if (product != null) {
            // 写入缓存,随机过期时间
            int expireTime = 3600 + new Random().nextInt(900);
            redisTemplate.opsForValue().set(key, JSON.toJSONString(product), expireTime, TimeUnit.SECONDS);
        }
        
        return product;
    }
}

5.4 缓存一致性

5.4.1 问题描述

缓存一致性是指如何确保数据库中的数据和缓存中的数据保持一致,特别是在数据更新时。

5.4.2 解决方案

1. Cache Aside Pattern (旁路缓存模式)

读取时,先查缓存,缓存没有再查数据库,并写入缓存。
更新时,先更新数据库,再删除缓存。

@Service
public class CacheAsideService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private UserMapper userMapper;
    
    public User getUser(Long id) {
        String key = "user:" + id;
        
        // 先查缓存
        String userJson = redisTemplate.opsForValue().get(key);
        if (userJson != null) {
            return JSON.parseObject(userJson, User.class);
        }
        
        // 查数据库
        User user = userMapper.selectById(id);
        
        // 写入缓存
        if (user != null) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 1, TimeUnit.HOURS);
        }
        
        return user;
    }
    
    @Transactional
    public void updateUser(User user) {
        // 先更新数据库
        userMapper.updateById(user);
        
        // 再删除缓存
        String key = "user:" + user.getId();
        redisTemplate.delete(key);
    }
}

2. 延迟双删策略

为了解决先更新数据库再删除缓存时可能出现的并发问题,采用延迟双删策略。

@Service
public class DelayDoubleDeleteService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private ProductMapper productMapper;
    
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    
    @Transactional
    public void updateProduct(Product product) {
        String key = "product:" + product.getId();
        
        // 先删除缓存
        redisTemplate.delete(key);
        
        // 再更新数据库
        productMapper.updateById(product);
        
        // 延迟一段时间后再次删除缓存
        threadPoolExecutor.execute(() -> {
            try {
                // 延迟500ms再次删除
                Thread.sleep(500);
                redisTemplate.delete(key);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }
}

3. 消息队列保证最终一致性

使用消息队列来保证数据库和缓存的最终一致性。

@Service
public class MQCacheService {
    @Autowired
    private ProductMapper productMapper;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    // 更新商品信息
    @Transactional
    public void updateProduct(Product product) {
        // 更新数据库
        productMapper.updateById(product);
        
        // 发送消息到消息队列
        CacheMessage message = new CacheMessage();
        message.setId(product.getId());
        message.setType("product");
        message.setOperation("update");
        
        rabbitTemplate.convertAndSend("cache.exchange", "cache.product.update", message);
    }
}

// 消息消费者
@Component
public class CacheMessageConsumer {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @RabbitListener(queues = "cache.product.update.queue")
    public void handleCacheMessage(CacheMessage message) {
        if ("product".equals(message.getType()) && "update".equals(message.getOperation())) {
            String key = "product:" + message.getId();
            // 删除缓存
            redisTemplate.delete(key);
        }
    }
}

5.5 热点数据处理

5.5.1 问题描述

热点数据是指被频繁访问的数据,如果这些数据集中访问,可能导致缓存服务压力过大。

5.5.2 解决方案

1. 本地缓存 + Redis缓存

将热点数据不仅存入Redis,还可以存入本地缓存,减轻Redis的压力。

@Configuration
public class CacheConfig {
    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .maximumSize(10000)
                .build();
    }
}

@Service
public class MultilevelCacheService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private Cache<String, Object> caffeineCache;
    
    @Autowired
    private ProductMapper productMapper;
    
    public Product getProduct(Long id) {
        String key = "product:" + id;
        
        // 先查本地缓存
        Product product = (Product) caffeineCache.getIfPresent(key);
        if (product != null) {
            return product;
        }
        
        // 查Redis缓存
        String productJson = redisTemplate.opsForValue().get(key);
        if (productJson != null) {
            product = JSON.parseObject(productJson, Product.class);
            // 放入本地缓存
            caffeineCache.put(key, product);
            return product;
        }
        
        // 查数据库
        product = productMapper.selectById(id);
        
        if (product != null) {
            // 放入Redis
            redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 1, TimeUnit.HOURS);
            // 放入本地缓存
            caffeineCache.put(key, product);
        }
        
        return product;
    }
}

2. 热点数据分片

将热点key进行hash分片,减轻单个key的访问压力。

@Service
public class KeyShardingService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final int SHARDING_COUNT = 10;
    
    public String getHotData(String hotKey) {
        // 随机选择一个分片
        int shardingIndex = ThreadLocalRandom.current().nextInt(SHARDING_COUNT);
        String shardingKey = hotKey + ":" + shardingIndex;
        
        String value = redisTemplate.opsForValue().get(shardingKey);
        
        if (value != null) {
            return value;
        }
        
        // 查询实际数据(例如从数据库)
        String actualData = queryActualData(hotKey);
        
        // 将数据写入所有分片
        for (int i = 0; i < SHARDING_COUNT; i++) {
            String key = hotKey + ":" + i;
            redisTemplate.opsForValue().set(key, actualData, 1, TimeUnit.HOURS);
        }
        
        return actualData;
    }
    
    private String queryActualData(String key) {
        // 从数据库或其他数据源查询数据
        return "Data for " + key;
    }
}

3. Redis集群水平扩展

通过增加Redis节点,使用Redis Cluster模式进行数据分片,提高集群整体承载能力。

5.6 大key问题

5.6.1 问题描述

大key指的是Redis中存储的value非常大的键值对,会导致Redis性能下降、内存占用不均匀、网络阻塞等问题。

5.6.2 解决方案

1. 大key检测

使用Redis的--bigkeys选项或Redis命令SCAN配合MEMORY USAGE来检测大key。

# 使用redis-cli检测大key
redis-cli --bigkeys -i 0.1

# 使用SCAN和MEMORY USAGE
redis-cli --scan --pattern '*' | head -n 100 | xargs -i redis-cli memory usage '{}'

2. 大key拆分

将大key拆分成多个小key,例如将一个大hash拆分成多个小hash。

@Service
public class BigKeyService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final int HASH_PARTITION_SIZE = 100;
    
    // 添加用户购物车商品
    public void addToCart(Long userId, Long productId, Integer count) {
        // 将userId按100取模分成多个小hash
        int shardingIndex = (int) (userId % HASH_PARTITION_SIZE);
        String cartKey = "cart:" + shardingIndex;
        
        // 生成field: userId_productId
        String field = userId + "_" + productId;
        
        redisTemplate.opsForHash().put(cartKey, field, count.toString());
    }
    
    // 获取用户购物车
    public Map<Long, Integer> getUserCart(Long userId) {
        // 计算sharding index
        int shardingIndex = (int) (userId % HASH_PARTITION_SIZE);
        String cartKey = "cart:" + shardingIndex;
        
        // 前缀匹配查询
        String fieldPrefix = userId + "_";
        
        // 获取所有的hash entries
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(cartKey);
        
        // 过滤并转换结果
        Map<Long, Integer> userCart = new HashMap<>();
        for (Map.Entry<Object, Object> entry : entries.entrySet()) {
            String field = entry.getKey().toString();
            if (field.startsWith(fieldPrefix)) {
                Long productId = Long.valueOf(field.substring(fieldPrefix.length()));
                Integer count = Integer.valueOf(entry.getValue().toString());
                userCart.put(productId, count);
            }
        }
        
        return userCart;
    }
}

3. 大集合序列化压缩存储

对于大的集合类型数据,可以序列化并压缩后作为字符串存储。

@Service
public class BigSetCompressionService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    // 存储大集合(用户关注列表)
    public void storeUserFollows(Long userId, Set<Long> followIds) {
        String key = "user:follows:" + userId;
        
        try {
            // 序列化Set为字节数组
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(followIds);
            oos.flush();
            byte[] bytes = bos.toByteArray();
            
            // 使用GZIP压缩
            ByteArrayOutputStream gzipBytes = new ByteArrayOutputStream();
            GZIPOutputStream gzipOS = new GZIPOutputStream(gzipBytes);
            gzipOS.write(bytes);
            gzipOS.finish();
            
            // 转Base64并存储
            String compressedData = Base64.getEncoder().encodeToString(gzipBytes.toByteArray());
            redisTemplate.opsForValue().set(key, compressedData);
            
        } catch (IOException e) {
            throw new RuntimeException("Failed to compress and store user follows", e);
        }
    }
    
    // 获取用户关注列表
    @SuppressWarnings("unchecked")
    public Set<Long> getUserFollows(Long userId) {
        String key = "user:follows:" + userId;
        String compressedData = redisTemplate.opsForValue().get(key);
        
        if (compressedData == null) {
            return Collections.emptySet();
        }
        
        try {
            // 解码Base64
            byte[] compressedBytes = Base64.getDecoder().decode(compressedData);
            
            // 解压GZIP
            ByteArrayInputStream bis = new ByteArrayInputStream(compressedBytes);
            GZIPInputStream gzipIS = new GZIPInputStream(bis);
            ByteArrayOutputStream uncompressedBos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = gzipIS.read(buffer)) > 0) {
                uncompressedBos.write(buffer, 0, len);
            }
            
            // 反序列化
            byte[] uncompressedBytes = uncompressedBos.toByteArray();
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(uncompressedBytes));
            return (Set<Long>) ois.readObject();
            
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Failed to decompress user follows", e);
        }
    }
}

5.7 Redis性能优化

5.7.1 连接池优化

合理配置Redis连接池,避免频繁创建和销毁连接。

@Configuration
public class RedisConfig {
    @Bean
    public LettuceConnectionFactory redisConnectionFactory(RedisProperties redisProperties) {
        // 构建Redis配置
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName(redisProperties.getHost());
        redisConfig.setPort(redisProperties.getPort());
        redisConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
        redisConfig.setDatabase(redisProperties.getDatabase());
        
        // 连接池配置
        LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(redisProperties.getTimeout().toMillis()))
                .poolConfig(getPoolConfig(redisProperties.getLettuce().getPool()))
                .build();
                
        return new LettuceConnectionFactory(redisConfig, clientConfig);
    }
    
    private GenericObjectPoolConfig getPoolConfig(RedisProperties.Pool properties) {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(properties.getMaxActive());
        config.setMaxIdle(properties.getMaxIdle());
        config.setMinIdle(properties.getMinIdle());
        config.setMaxWaitMillis(properties.getMaxWait().toMillis());
        return config;
    }
}

5.7.2 批量操作优化

使用pipeline或mget等批量操作命令,减少网络往返次数。

@Service
public class BatchOperationService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public List<String> batchGet(List<String> keys) {
        // 使用opsForValue().multiGet批量获取
        return redisTemplate.opsForValue().multiGet(keys);
    }
    
    public void batchSet(Map<String, String> keyValues) {
        // 使用pipeline批量设置
        redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                
                for (Map.Entry<String, String> entry : keyValues.entrySet()) {
                    stringRedisConn.set(entry.getKey(), entry.getValue());
                }
                
                return null;
            }
        });
    }
}

5.7.3 数据结构选择

为不同的业务场景选择合适的数据结构,例如:

  • String:简单的键值对
  • Hash:存储对象
  • List:队列或栈
  • Set:无序集合,去重
  • Sorted Set:有序集合,排名
@Service
public class DataStructureService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    // 使用Hash存储用户信息
    public void saveUserWithHash(User user) {
        String key = "user:hash:" + user.getId();
        Map<String, String> userMap = new HashMap<>();
        userMap.put("id", user.getId().toString());
        userMap.put("name", user.getName());
        userMap.put("email", user.getEmail());
        userMap.put("age", user.getAge().toString());
        
        redisTemplate.opsForHash().putAll(key, userMap);
        redisTemplate.expire(key, 1, TimeUnit.HOURS);
    }
    
    // 使用Sorted Set实现排行榜
    public void updateScore(Long userId, double score) {
        String key = "leaderboard";
        redisTemplate.opsForZSet().add(key, userId.toString(), score);
    }
    
    // 获取前N名
    public List<String> getTopN(int n) {
        String key = "leaderboard";
        Set<String> topUsers = redisTemplate.opsForZSet().reverseRange(key, 0, n - 1);
        return new ArrayList<>(topUsers);
    }
}

5.7.4 内存优化

合理设置数据过期时间,避免Redis内存过度使用。

@Service
public class MemoryOptimizationService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    // 设置合理的TTL
    public void setWithTTL(String key, String value, long ttlSeconds) {
        redisTemplate.opsForValue().set(key, value, ttlSeconds, TimeUnit.SECONDS);
    }
    
    // 定期清理过期数据
    @Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点执行
    public void cleanExpiredData() {
        // 随机执行EXPIRE命令
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                // 执行EXPIRE命令,让Redis触发lazy expiration
                String randomKey = connection.randomKey();
                if (randomKey != null) {
                    connection.ttl(randomKey.getBytes());
                }
                return null;
            }
        });
    }
}

5.8 Redis监控与运维

5.8.1 监控指标

需要关注的Redis监控指标:

  • 内存使用率
  • CPU使用率
  • 客户端连接数
  • 命令执行频率
  • 命令执行延迟
  • 命令执行错误
  • 命中率
  • 过期Key数量
  • 驱逐(Eviction)Key数量

5.8.2 监控工具

  1. Redis INFO命令:获取Redis服务器的各种统计信息
@Service
public class RedisMonitorService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public Map<String, Object> getRedisInfo() {
        Properties info = redisTemplate.execute(RedisConnection::info);
        Map<String, Object> result = new HashMap<>();
        
        // 提取关键指标
        if (info != null) {
            // 内存使用
            result.put("usedMemory", info.getProperty("used_memory_human"));
            result.put("maxMemory", info.getProperty("maxmemory_human"));
            
            // 连接信息
            result.put("connectedClients", info.getProperty("connected_clients"));
            
            // 命令统计
            result.put("totalCommands", info.getProperty("total_commands_processed"));
            
            // 键统计
            result.put("dbSize", info.getProperty("db0").split("=")[1].split(",")[0]);
            
            // 命中率
            long keyspaceHits = Long.parseLong(info.getProperty("keyspace_hits"));
            long keyspaceMisses = Long.parseLong(info.getProperty("keyspace_misses"));
            double hitRatio = keyspaceHits / (double) (keyspaceHits + keyspaceMisses) * 100;
            result.put("hitRatio", String.format("%.2f%%", hitRatio));
        }
        
        return result;
    }
}
  1. Prometheus + Grafana:实时监控Redis性能
@Configuration
public class PrometheusConfig {
    @Bean
    public MeterRegistry meterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
    
    @Bean
    public RedisMetricsCollector redisMetricsCollector(MeterRegistry meterRegistry, RedisConnectionFactory redisConnectionFactory) {
        return new RedisMetricsCollector(meterRegistry, redisConnectionFactory);
    }
}

@Component
public class RedisMetricsCollector {
    private final RedisConnectionFactory redisConnectionFactory;
    private final MeterRegistry meterRegistry;
    
    public RedisMetricsCollector(MeterRegistry meterRegistry, RedisConnectionFactory redisConnectionFactory) {
        this.meterRegistry = meterRegistry;
        this.redisConnectionFactory = redisConnectionFactory;
        
        Gauge.builder("redis.memory.used", this, RedisMetricsCollector::getUsedMemory)
             .description("Redis used memory")
             .register(meterRegistry);
             
        Gauge.builder("redis.clients.connected", this, RedisMetricsCollector::getConnectedClients)
             .description("Redis connected clients")
             .register(meterRegistry);
             
        // ... 更多监控指标
    }
    
    private double getUsedMemory() {
        Properties info = getRedisInfo();
        if (info != null) {
            String usedMemory = info.getProperty("used_memory");
            return Double.parseDouble(usedMemory);
        }
        return 0;
    }
    
    private double getConnectedClients() {
        Properties info = getRedisInfo();
        if (info != null) {
            String clients = info.getProperty("connected_clients");
            return Double.parseDouble(clients);
        }
        return 0;
    }
    
    private Properties getRedisInfo() {
        return redisConnectionFactory.getConnection().info();
    }
}

5.8.3 持久化策略调优

根据业务需求和服务器配置,调整Redis的持久化策略。

1. RDB配置优化

# 在配置文件redis.conf中
save 900 1      # 900秒内至少有1个key变更,则触发保存
save 300 10     # 300秒内至少有10个key变更,则触发保存
save 60 10000   # 60秒内至少有10000个key变更,则触发保存

# 是否压缩RDB文件
rdbcompression yes

# RDB文件名
dbfilename dump.rdb

# RDB文件保存目录
dir /var/lib/redis

2. AOF配置优化

# 在配置文件redis.conf中
# 开启AOF
appendonly yes

# AOF文件名
appendfilename "appendonly.aof"

# 同步策略: always, everysec, no
appendfsync everysec

# AOF重写触发条件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

5.8.4 备份与恢复

定期备份Redis数据,确保数据安全。

@Component
public class RedisBackupService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Value("${redis.backup.path}")
    private String backupPath;
    
    // 触发RDB备份
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    public void backupRedisData() {
        redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                connection.save();
                return "OK";
            }
        });
        
        // 可以添加将RDB文件复制到备份服务器的逻辑
    }
    
    // 恢复数据
    public boolean restoreFromBackup(String backupFile) {
        // 实现从备份文件恢复的逻辑
        // 通常需要停止Redis服务,替换RDB文件,然后重启服务
        return true;
    }
}

5.8.5 扩容与缩容

合理规划Redis集群的扩容和缩容流程,确保服务平稳过渡。

1. 扩容步骤

  1. 准备新的Redis节点
  2. 配置与现有集群一致的配置
  3. 将新节点加入集群
  4. 重新分配槽位(Slot)
  5. 迁移数据
  6. 验证集群状态

2. 缩容步骤

  1. 确认要移除的节点
  2. 将该节点的槽位迁移到其他节点
  3. 从集群中移除节点
  4. 验证集群状态

6. Redis 与 Spring Boot 集成的高级特性

除了基本数据类型操作外,Spring Boot与Redis集成还提供了许多高级特性,满足复杂场景需求。

6.1 Spring Cache集成Redis

6.2 分布式锁

6.3 布隆过滤器

Redisson提供了布隆过滤器的实现,可以用于大规模数据的高效存在性检查。

@Service
public class BloomFilterService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public void initBloomFilter() {
        // 创建布隆过滤器,设置预计元素数量和误报率
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("user_ids");
        // 初始化:预计元素数量为100万,误判率为0.01
        bloomFilter.tryInit(1000000L, 0.01);
        
        System.out.println("布隆过滤器初始化完成");
    }
    
    public void addToBloomFilter(String userId) {
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("user_ids");
        bloomFilter.add(userId);
        System.out.println("添加用户ID到布隆过滤器: " + userId);
    }
    
    public boolean mightExist(String userId) {
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("user_ids");
        boolean exists = bloomFilter.contains(userId);
        System.out.println("检查用户ID是否可能存在: " + userId + ", 结果: " + exists);
        return exists;
    }
}

6.4 批量操作与管道

6.5 GEO地理位置

6.6 实时统计与计数

6.7 Redis Stream

内容太多了,详细请下载redis.md查看

你可能感兴趣的:(数据库,缓存,spring,boot,java,redis,性能优化)