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 基本命令示例
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
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示例
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 扩容与缩容
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
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 优化键值大小
8.1 商城秒杀系统
8.1.1 系统架构设计
8.1.2 Redis在秒杀系统中的应用
8.1.3 秒杀系统的性能优化
8.2 社交网络应用
8.2.1 用户关系存储
8.2.2 Feed流实现
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应用进行基准测试
总结
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数据迁移
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 分布式锁和分布式会话
总结
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 实战案例总结
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 备份与恢复最佳实践
14.1 Redis核心价值总结
14.2 主要章节回顾
14.3 Redis最佳实践汇总
14.3.1 设计与开发最佳实践
14.4 扩展学习资源
14.5 结语
Redis是一个开源的、基于内存的数据结构存储系统,可以用作数据库、缓存和消息中间件。Redis支持多种数据类型,如字符串、哈希、列表、集合、有序集合等,并提供了丰富的操作命令。
Redis(Remote Dictionary Server)是一个高性能的键值对数据库,具有以下特点:
相比传统关系型数据库,Redis具有更高的性能和更灵活的数据结构,适用于缓存、会话存储、消息队列、排行榜等场景。
特性 | Redis | 传统关系型数据库(如MySQL) |
---|---|---|
数据存储方式 | 内存为主,可持久化到磁盘 | 磁盘存储 |
数据结构 | 多种数据结构 | 表结构 |
事务 | 支持简单事务 | 完整ACID事务 |
性能 | 极高(10万QPS) | 中等(1万QPS) |
操作复杂度 | 简单 | 复杂 |
容量 | 受内存限制 | 受磁盘限制 |
扩展性 | 主从、集群 | 主从、分库分表 |
应用场景 | 缓存、计数器、排行榜、会话存储 | 结构化数据存储、复杂查询 |
# 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
# 拉取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
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
# 本地连接
redis-cli
# 远程连接
redis-cli -h 192.168.1.100 -p 6379 -a password
# 安全连接(带密码)
redis-cli -a password
# 设置和获取键值
SET name "John"
GET name
# 删除键
DEL name
# 检查键是否存在
EXISTS name
# 设置过期时间(秒)
SET token "12345" EX 3600
# 获取所有键
KEYS *
# 获取键类型
TYPE name
# 清空当前数据库
FLUSHDB
# 清空所有数据库
FLUSHALL
Spring Boot提供了Redis的自动配置功能,可以轻松地将Redis集成到应用程序中。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
在application.properties
或application.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 # 连接池中的最小空闲连接
创建RedisTemplate
和StringRedisTemplate
的配置类:
@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;
}
}
@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);
}
}
@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);
}
}
@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);
}
}
@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);
}
}
@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);
}
}
@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);
}
}
Redis支持多种数据类型,每种类型都有特定的操作命令和应用场景。本章将详细介绍Redis的五种基本数据类型以及其他扩展数据类型。
字符串是Redis中最基本的数据类型,可以存储文本、整数或二进制数据。
# 设置值
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
@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);
}
}
哈希是一个string类型的field和value的映射表,特别适合用于存储对象。
# 设置单个哈希字段
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
@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);
}
}
列表是简单的字符串列表,按照插入顺序排序,支持从两端操作元素。
# 从左端插入元素
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
@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);
}
}
集合是无序的字符串集合,不允许有重复成员。
# 添加一个或多个元素
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 ...]
@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);
}
}
有序集合与集合类似,但每个成员都关联一个分数,根据分数排序。
# 添加成员和分数
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
@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);
}
}
除了基本的数据类型和操作外,Redis还提供了许多高级特性,可以满足更复杂的应用场景。
Redis事务允许执行一组命令,确保他们被顺序执行且不被其他客户端命令打断。
# 开始事务
MULTI
# 在事务中添加命令
(命令会被入队但不执行)
# 执行事务
EXEC
# 取消事务
DISCARD
# 监控键,如果键在EXEC前被修改,事务将被取消
WATCH key [key ...]
# 取消所有监控
UNWATCH
@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();
}
});
}
}
Redis提供了发布订阅功能,允许消息发布者将消息发送到指定的频道,订阅该频道的所有客户端都可以接收到消息。
# 发布消息
PUBLISH channel message
# 订阅频道
SUBSCRIBE channel [channel ...]
# 取消订阅
UNSUBSCRIBE [channel [channel ...]]
# 按模式订阅频道
PSUBSCRIBE pattern [pattern ...]
# 取消按模式订阅
PUNSUBSCRIBE [pattern [pattern ...]]
# 查看订阅数量
PUBSUB NUMSUB [channel ...]
# 查看匹配模式的频道
PUBSUB CHANNELS [pattern]
@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));
}
}
Redis支持使用Lua脚本执行原子操作,可以避免多次网络往返并确保操作的原子性。
# 执行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
限流器脚本:
-- 限流脚本
-- 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
@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);
}
}
Redis提供了两种持久化方式:RDB和AOF,以确保数据不会因服务器重启而丢失。
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
优点:
缺点:
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
优点:
缺点:
Redis 4.0引入了混合持久化模式,结合了RDB和AOF的优点:
# 开启混合持久化(Redis 4.0+)
aof-use-rdb-preamble yes
这种模式下,AOF重写时会将已有数据以RDB格式写入,后续命令仍以AOF格式追加,既保证了恢复速度,又降低了数据丢失风险。
Redis提供了多种方式实现集群和高可用配置。
主从复制提供了数据的冗余备份,也是高可用的基础。
配置方式:
主节点配置:
# 主节点不需要特殊配置
从节点配置:
# 配置主节点IP和端口
replicaof 192.168.1.1 6379
# 如果主节点有密码
masterauth "master-password"
# 从节点是否可以接受写命令(默认只读)
replica-read-only yes
特点:
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
特点:
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
特点:
单节点配置:
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);
}
}
Redis 6.0及以上版本引入了一系列重要的新特性,这些特性在安全性、性能和功能性上都有很大提升。
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
Redis 6.0引入了客户端缓存功能,允许客户端在本地缓存数据,减少网络请求。
两种模式:
在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;
}
}
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%,特别是对于大数据包的处理更有优势。
Redis 6.0引入了新的RESP3协议,提供了更丰富的数据类型和更好的错误处理。
主要改进:
在Spring Boot中使用RESP3:
spring:
redis:
lettuce:
client-options:
protocol: resp3
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);
}
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的功能和性能,建议在实际项目中根据需要进行合理配置和使用。
缓存穿透是指查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求数据库,如果有恶意请求故意查询不存在的数据,会导致数据库压力过大。
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;
}
}
缓存击穿是指热点key在过期的一瞬间,同时有大量的请求打到了数据库上,导致数据库压力瞬间增大。
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);
}
}
}
缓存雪崩是指在某一个时间段内,大量的缓存集中过期或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
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;
}
}
缓存一致性是指如何确保数据库中的数据和缓存中的数据保持一致,特别是在数据更新时。
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);
}
}
}
热点数据是指被频繁访问的数据,如果这些数据集中访问,可能导致缓存服务压力过大。
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模式进行数据分片,提高集群整体承载能力。
大key指的是Redis中存储的value非常大的键值对,会导致Redis性能下降、内存占用不均匀、网络阻塞等问题。
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);
}
}
}
合理配置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;
}
}
使用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;
}
});
}
}
为不同的业务场景选择合适的数据结构,例如:
@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);
}
}
合理设置数据过期时间,避免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;
}
});
}
}
需要关注的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;
}
}
@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();
}
}
根据业务需求和服务器配置,调整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
定期备份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;
}
}
合理规划Redis集群的扩容和缩容流程,确保服务平稳过渡。
1. 扩容步骤:
2. 缩容步骤:
除了基本数据类型操作外,Spring Boot与Redis集成还提供了许多高级特性,满足复杂场景需求。
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;
}
}