Redis 进阶知识
Redis 进阶知识
一、持久化详解
什么是持久化
持久化是将内存中的数据保存到磁盘的过程,防止数据丢失。Redis 是内存数据库,重启后数据会丢失,因此需要持久化机制。
为什么需要持久化:
- 数据安全:防止进程退出或服务器宕机导致数据丢失
- 快速恢复:重启后可以从磁盘恢复数据
- 备份:可以将持久化文件备份到其他地方
Redis 持久化方式:
- RDB(Redis Database):快照方式,保存某个时间点的完整数据
- AOF(Append Only File):日志方式,记录每个写操作
- 混合持久化:RDB + AOF,结合两者优点(Redis 4.0+)
RDB 持久化
什么是 RDB:
RDB 是 Redis 的默认持久化方式,通过创建数据快照(Snapshot)将内存数据保存到磁盘。
工作原理:
- Redis 调用 fork() 创建子进程
- 子进程将数据写入临时 RDB 文件
- 写入完成后,用新文件替换旧文件
- 主进程继续处理命令(Copy-On-Write 机制)
RDB 通过快照方式将内存数据保存到磁盘。
配置方式:
# redis.conf
save 900 1 # 900秒内至少1次修改
save 300 10 # 300秒内至少10次修改
save 60 10000 # 60秒内至少10000次修改
dbfilename dump.rdb
dir /var/lib/redis手动触发:
SAVE # 同步保存,阻塞主线程
BGSAVE # 后台异步保存(推荐)优缺点:
- ✅ 文件紧凑,恢复速度快
- ✅ 适合备份和灾难恢复
- ❌ 可能丢失最后一次快照后的数据
- ❌ fork 子进程时可能造成短暂阻塞
AOF 持久化
什么是 AOF:
AOF(Append Only File)以日志的形式记录每个写操作命令,重启时重新执行这些命令来恢复数据。
工作原理:
- 客户端发送写命令
- Redis 将命令追加到 AOF 缓冲区
- 根据策略将缓冲区内容同步到磁盘
- 定期进行 AOF 重写,压缩文件大小
AOF 重写:
- 随着写操作增多,AOF 文件会越来越大
- 重写会遍历内存数据,生成最小的命令集
- 例如:100 次 INCR 可以重写为 1 次 SET
AOF 记录每个写操作命令,重启时重放恢复数据。
配置方式:
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
# 同步策略
appendfsync always # 每次写入都同步(最安全,最慢)
appendfsync everysec # 每秒同步一次(推荐)
appendfsync no # 由操作系统决定(最快,可能丢数据)AOF 重写:
BGREWRITEAOF # 手动触发重写
# 自动重写配置
auto-aof-rewrite-percentage 100 # 文件增长100%时重写
auto-aof-rewrite-min-size 64mb # 最小64MB才重写混合持久化(Redis 4.0+)
结合 RDB 和 AOF 的优点。
aof-use-rdb-preamble yesAOF 文件前半部分是 RDB 格式,后半部分是 AOF 增量命令。
二、主从复制
什么是主从复制
主从复制是 Redis 实现高可用的基础,通过将数据从主节点(Master)复制到从节点(Slave),实现数据备份和读写分离。
作用:
- 数据备份:从节点保存主节点的数据副本
- 读写分离:主节点处理写操作,从节点处理读操作
- 高可用基础:主节点故障时,从节点可以升级为主节点
复制类型:
- 全量复制:首次连接或长时间断线后,传输完整数据
- 增量复制:正常运行时,只传输增量命令
- 部分复制:短时间断线后,传输断线期间的命令
配置主从
从节点配置:
# redis.conf
replicaof 192.168.1.100 6379
# 或运行时设置
REPLICAOF 192.168.1.100 6379查看复制状态:
INFO replication复制原理
- 全量复制:从节点首次连接,主节点执行 BGSAVE 生成 RDB 发送
- 增量复制:主节点将写命令发送到复制缓冲区,从节点读取执行
- 断线重连:通过复制偏移量(offset)进行增量同步
主节点 从节点
| |
|<--- PSYNC ? -1 ---------| (首次连接)
|---- +FULLRESYNC ------->|
|---- RDB 文件 ---------->|
|---- 增量命令 ---------->|
| |读写分离
// 主节点写
masterJedis.set("key", "value");
// 从节点读
String value = slaveJedis.get("key");三、哨兵模式(Sentinel)
什么是哨兵模式
哨兵(Sentinel)是 Redis 的高可用解决方案,用于监控主从节点,实现自动故障转移。
核心功能:
- 监控(Monitoring):检查主从节点是否正常运行
- 通知(Notification):通过 API 通知管理员或其他程序
- 自动故障转移(Automatic Failover):主节点故障时,自动将从节点升级为主节点
- 配置提供(Configuration Provider):客户端通过哨兵获取当前主节点地址
工作原理:
- 多个哨兵节点监控同一个主节点
- 哨兵之间通过 Gossip 协议通信
- 主节点下线时,哨兵投票选举新主节点
哨兵用于监控主从节点,实现自动故障转移。
配置哨兵
# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000参数说明:
monitor:监控的主节点,2 表示需要 2 个哨兵同意才能故障转移down-after-milliseconds:主节点无响应多久判定为下线parallel-syncs:故障转移时同时同步的从节点数failover-timeout:故障转移超时时间
启动哨兵
redis-sentinel sentinel.conf
# 或
redis-server sentinel.conf --sentinelJava 连接哨兵
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.1.101:26379");
sentinels.add("192.168.1.102:26379");
sentinels.add("192.168.1.103:26379");
JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels);
try (Jedis jedis = pool.getResource()) {
jedis.set("key", "value");
}四、Redis Cluster 集群
什么是 Redis Cluster
Redis Cluster 是 Redis 的分布式解决方案,通过数据分片实现水平扩展和高可用。
核心特性:
- 数据分片:将数据分散到多个节点,突破单机内存限制
- 去中心化:节点之间平等,没有中心节点
- 高可用:主节点故障时,从节点自动升级
- 自动故障转移:无需哨兵,集群自己完成故障转移
槽位(Slot)机制:
- 集群有 16384 个槽位(0-16383)
- 每个 Key 通过 CRC16 算法计算属于哪个槽位
- 每个节点负责一部分槽位
- 公式:
HASH_SLOT = CRC16(key) % 16384
集群架构
- 数据分片:16384 个槽位(slot),每个节点负责一部分
- 去中心化:节点间通过 Gossip 协议通信
- 高可用:每个主节点配备从节点
创建集群
# 创建6个节点(3主3从)
redis-cli --cluster create \
192.168.1.101:6379 \
192.168.1.102:6379 \
192.168.1.103:6379 \
192.168.1.104:6379 \
192.168.1.105:6379 \
192.168.1.106:6379 \
--cluster-replicas 1集群命令
# 查看集群信息
CLUSTER INFO
# 查看节点
CLUSTER NODES
# 查看槽位分配
CLUSTER SLOTS
# 计算 Key 属于哪个槽
CLUSTER KEYSLOT mykeyJava 连接集群
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.101", 6379));
nodes.add(new HostAndPort("192.168.1.102", 6379));
nodes.add(new HostAndPort("192.168.1.103", 6379));
JedisCluster cluster = new JedisCluster(nodes);
cluster.set("key", "value");
String value = cluster.get("key");集群限制
- 不支持多 Key 操作(除非在同一槽位)
- 不支持多数据库(只有 db0)
- 事务只能在单节点执行
Hash Tag:强制 Key 分配到同一槽位
SET {user:1000}.name "张三"
SET {user:1000}.age "25"
# 这两个 Key 会分配到同一槽位五、Lua 脚本
什么是 Lua 脚本
Lua 是一种轻量级脚本语言,Redis 2.6+ 支持在服务器端执行 Lua 脚本。
为什么用 Lua:
- 原子性:脚本中的所有命令作为一个整体执行,不会被其他命令打断
- 减少网络开销:多个命令一次发送,减少往返次数
- 复用:脚本可以缓存在服务器端,通过 SHA1 值调用
- 复杂逻辑:可以在服务器端实现复杂的业务逻辑
使用场景:
- 分布式锁
- 限流算法
- 原子性的复合操作
- 复杂的数据处理
为什么用 Lua?
- 原子性:脚本中的命令作为整体执行
- 减少网络开销:多个命令一次发送
- 复用:脚本可缓存重复使用
基本语法
-- 获取参数
local key = KEYS[1]
local value = ARGV[1]
-- 调用 Redis 命令
redis.call('SET', key, value)
local result = redis.call('GET', key)
return result执行脚本
# EVAL 执行
EVAL "return redis.call('GET', KEYS[1])" 1 mykey
# EVALSHA 执行(使用脚本 SHA1)
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
EVALSHA <sha1> 1 mykeyJava 执行 Lua
String script =
"local current = redis.call('GET', KEYS[1]) " +
"if current then " +
" return redis.call('INCR', KEYS[1]) " +
"else " +
" redis.call('SET', KEYS[1], ARGV[1]) " +
" return ARGV[1] " +
"end";
Object result = jedis.eval(script,
Collections.singletonList("counter"),
Collections.singletonList("1"));实战:分布式锁
-- 加锁
local key = KEYS[1]
local value = ARGV[1]
local ttl = ARGV[2]
if redis.call('SETNX', key, value) == 1 then
redis.call('EXPIRE', key, ttl)
return 1
end
return 0-- 解锁(验证持有者)
local key = KEYS[1]
local value = ARGV[1]
if redis.call('GET', key) == value then
return redis.call('DEL', key)
end
return 0六、内存管理
什么是内存管理
Redis 是内存数据库,需要合理管理内存使用,防止内存溢出。
内存管理策略:
- 设置最大内存:
maxmemory配置 - 内存淘汰策略:内存满时如何删除数据
- 过期键删除:如何删除过期的键
- 内存优化:使用合适的数据结构
内存使用分析:
- 数据本身
- 键值对象的元数据
- 缓冲区(客户端缓冲区、复制缓冲区、AOF 缓冲区)
- 内存碎片
内存淘汰策略
# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru| 策略 | 说明 |
|---|---|
| noeviction | 不淘汰,内存满时报错 |
| allkeys-lru | 所有 Key 中淘汰 LRU |
| volatile-lru | 有过期时间的 Key 中淘汰 LRU |
| allkeys-lfu | 所有 Key 中淘汰 LFU(4.0+) |
| volatile-lfu | 有过期时间的 Key 中淘汰 LFU |
| allkeys-random | 随机淘汰 |
| volatile-random | 有过期时间的 Key 中随机淘汰 |
| volatile-ttl | 淘汰即将过期的 Key |
内存分析
# 查看内存使用
INFO memory
# 分析大 Key
redis-cli --bigkeys
# 内存采样分析(4.0+)
MEMORY USAGE key
MEMORY DOCTOR过期键删除策略
- 惰性删除:访问时检查是否过期
- 定期删除:每 100ms 随机检查一批 Key
七、事务与管道
什么是 Redis 事务
Redis 事务是一组命令的集合,这些命令会按顺序执行,执行过程中不会被其他客户端的命令打断。
特点:
- 批量执行:一次性执行多个命令
- 顺序执行:按照命令的顺序依次执行
- 原子性:要么全部执行,要么全部不执行(但不支持回滚)
- 隔离性:执行过程中不会被其他命令打断
注意:
- Redis 事务不支持回滚!
- 命令语法错误会导致整个事务失败
- 运行时错误不会回滚,会继续执行后续命令
事务
MULTI # 开启事务
SET key1 value1
SET key2 value2
INCR counter
EXEC # 执行事务
# 或 DISCARD # 取消事务注意:Redis 事务不支持回滚!
WATCH 乐观锁
WATCH balance
val = GET balance
val = val - 100
MULTI
SET balance $val
EXEC # 如果 balance 被其他客户端修改,EXEC 返回 nilPipeline 管道
什么是 Pipeline:
Pipeline(管道)是一种批量执行命令的方式,可以一次性发送多个命令,减少网络往返次数。
Pipeline vs 事务:
- Pipeline:只是批量发送,不保证原子性
- 事务:保证原子性和隔离性
使用场景:
- 批量插入数据
- 批量查询数据
- 不需要原子性的批量操作
批量发送命令,减少网络往返。
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 1000; i++) {
pipeline.set("key" + i, "value" + i);
}
List<Object> results = pipeline.syncAndReturnAll();八、发布订阅
什么是发布订阅
发布订阅(Pub/Sub)是一种消息通信模式,发送者(Publisher)发送消息,订阅者(Subscriber)接收消息。
特点:
- 解耦:发布者和订阅者不需要知道对方的存在
- 一对多:一个消息可以被多个订阅者接收
- 实时性:消息实时推送
- 不可靠:消息不持久化,订阅者离线会丢失消息
使用场景:
- 实时消息推送
- 聊天室
- 实时通知
- 事件驱动
注意:
- 消息不会持久化
- 订阅者离线时会丢失消息
- 不适合需要可靠性的场景(建议使用 Stream 或 MQ)
基本使用
# 订阅频道
SUBSCRIBE channel1 channel2
# 发布消息
PUBLISH channel1 "Hello"
# 模式订阅
PSUBSCRIBE news.*Java 实现
// 订阅者
jedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
System.out.println(channel + ": " + message);
}
}, "channel1");
// 发布者
jedis.publish("channel1", "Hello World");九、Stream(Redis 5.0+)
什么是 Stream
Stream 是 Redis 5.0 引入的新数据结构,专门用于消息队列场景,弥补了 Pub/Sub 和 List 的不足。
特点:
- 持久化:消息会持久化到磁盘
- 消费者组:支持多个消费者组,每个组独立消费
- 消息确认:支持 ACK 机制,确保消息被处理
- 消息回溯:可以从任意位置开始消费
- 阻塞读取:支持阻塞等待新消息
vs List:
- List 不支持消费者组
- List 消息被消费后就删除了
vs Pub/Sub:
- Pub/Sub 消息不持久化
- Pub/Sub 不支持消息确认
Stream 是 Redis 5.0 引入的消息队列数据结构。
基本操作
# 添加消息
XADD mystream * field1 value1 field2 value2
# 读取消息
XREAD COUNT 10 STREAMS mystream 0
# 创建消费者组
XGROUP CREATE mystream mygroup 0
# 消费者读取
XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >
# 确认消息
XACK mystream mygroup 1526569495631-0与 List 对比
| 特性 | List | Stream |
|---|---|---|
| 消息持久化 | ✅ | ✅ |
| 消费者组 | ❌ | ✅ |
| 消息确认 | ❌ | ✅ |
| 消息回溯 | ❌ | ✅ |
| 阻塞读取 | ✅ | ✅ |
十、性能优化
1. 合理使用数据结构
# 小数据量用 ziplist
hash-max-ziplist-entries 512
hash-max-ziplist-value 642. 避免大 Key
- String 类型 < 10KB
- Hash/List/Set/ZSet 元素数 < 5000
3. 批量操作
# 使用 MSET/MGET
MSET key1 val1 key2 val2 key3 val3
MGET key1 key2 key3
# 使用 Pipeline4. 连接池配置
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100); // 最大连接数
config.setMaxIdle(20); // 最大空闲连接
config.setMinIdle(5); // 最小空闲连接
config.setMaxWaitMillis(3000); // 最大等待时间
config.setTestOnBorrow(true); // 借用时测试连接5. 慢查询日志
# 配置
slowlog-log-slower-than 10000 # 超过10ms记录
slowlog-max-len 128 # 最多保存128条
# 查看
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET