Lesson 63 · 数据库与中间件实战

Redis 持久化与高可用:RDB、AOF 与 Cluster 集群

高级·🔥 极高·#Redis·#持久化·#集群

高级 · 面试极高

Redis 持久化与高可用:RDB、AOF 与 Cluster 集群

RDB 快照 vs AOF 追加日志、混合持久化。哨兵模式 vs Cluster 分片集群。Redis 单线程为什么这么快?

RDBAOF混合持久化SentinelCluster单线程模型
第 1 站

Redis 单线程为什么这么快?

很多人疑惑:Redis 是单线程的,为什么还能达到 10 万+ QPS?答案是:瓶颈不在 CPU,在网络和内存

Redis 单线程高性能模型 Client 1 Client 2 Client N I/O 多路复用 epoll / kqueue 监听多个 socket 事件 单线程命令执行器 串行执行 GET/SET/... 纯内存操作,无锁竞争 快的三大原因 ① 纯内存操作 — 数据在 RAM 中,读写纳秒级 ② I/O 多路复用 — 一个线程同时处理数千个连接(epoll),避免阻塞 ③ 单线程无锁竞争 — 无需加锁/解锁/上下文切换
图 1-1 Redis 单线程模型:epoll 多路复用 + 内存操作 + 无锁

Redis 6.0 引入了多线程,是怎么回事?

Redis 6.0 的多线程只用于网络 I/O(读写 socket、解析协议),命令执行仍然是单线程的。这是为了解决网络 I/O 成为瓶颈的问题(高并发时网络数据拷贝占用了大量 CPU)。所以"Redis 命令执行是单线程的"这个结论依然成立。

关键结论

Redis 快的本质:内存操作(ns级)+ epoll 多路复用(高并发)+ 单线程(无锁开销)。单线程不是"慢"的原因——瓶颈在网络/内存,不在 CPU。

第 2 站

RDB 快照——某一时刻的全量内存镜像

RDB(Redis Database)是 Redis 的默认持久化方式:在指定时间间隔内将内存中的全部数据快照写入磁盘的 .rdb 文件。

RDB 快照流程(BGSAVE) Redis 主进程 继续处理客户端命令 fork() 后不阻塞 写时复制(COW)保证数据隔离 fork() 子进程(bgSave) 将内存快照写入 dump.rdb 写完后退出,通知主进程 临时替换旧的 .rdb 文件 Copy-On-Write(写时复制): fork 后父子共享物理内存页。主进程修改某页时,OS 才复制该页 → 不额外占内存
图 2-1 BGSAVE:fork 子进程写快照,主进程继续服务
RDB 触发条件配置
# redis.conf
save 900 1      # 900 秒内至少 1 次写操作 → 触发 BGSAVE
save 300 10     # 300 秒内至少 10 次写操作
save 60 10000   # 60 秒内至少 10000 次写操作

# 手动触发
SAVE    # 阻塞主进程(生产慎用)
BGSAVE  # fork 子进程(推荐)
优点缺点
文件紧凑,恢复速度快两次快照之间的数据可能丢失
fork 子进程不影响主进程服务fork 大内存进程时可能阻塞毫秒级
适合备份和灾难恢复频繁 fork 有 CPU 和内存开销
第 3 站

AOF 追加日志——记录每一条写命令

AOF(Append Only File)记录的是每一条写命令(SET/DEL/HSET 等),以 Redis 协议格式追加到 .aof 文件末尾。

AOF 文件格式(RESP 协议)
# appendonly.aof 文件内容示例
*2
$6
SELECT
$1
0
*3
$3
SET
$3
foo
$3
bar
*2
$3
DEL
$3
foo

# 恢复时重放这些命令 → 还原数据
AOF 三种 fsync 策略 always 每条命令都 fsync 数据最安全 性能最差(IO密集) everysec(默认) 每秒 fsync 一次 最多丢 1 秒数据 性能和安全折中 no 由 OS 决定 fsync 可能丢很多数据 性能最好 生产环境推荐 everysec,兼顾安全和性能
图 3-1 AOF 三种 fsync 策略对比

AOF 文件越来越大怎么办?

Redis 提供了 AOF 重写(BGREWRITEAOF)机制:fork 子进程,根据当前内存数据生成"最小等价命令集",替换旧的 AOF 文件。例如对 key "x" 执行了 100 次 SET,重写后只保留最后一次。重写过程中新产生的命令会同时写入 AOF 重写缓冲区,完成后合并。

对比维度RDBAOF
数据安全性可能丢失两次快照间的数据取决于 fsync 策略(everysec 最多丢 1 秒)
文件大小紧凑(二进制压缩)较大(文本格式命令)
恢复速度快(直接加载二进制)慢(需重放所有命令)
写入性能fork 时有瞬间阻塞fsync 有持续 IO 开销
适用场景备份、灾难恢复、冷备数据安全要求高的生产环境
推荐策略每小时一次定时备份always/everysec + 自动重写
AOF 重写配置
# 自动触发 AOF 重写的条件
auto-aof-rewrite-percentage 100  # AOF 文件增长 100% 时触发
auto-aof-rewrite-min-size 64mb  # 最小 64MB 才触发(避免频繁重写小文件)

# 手动触发
BGREWRITEAOF

# 查看 AOF 状态
INFO persistence
# aof_enabled:1
# aof_rewrite_in_progress:0
# aof_current_size:104857600  (当前 AOF 文件大小)
# aof_base_size:52428800     (上次重写后的大小)
第 4 站

混合持久化(Redis 4.0+)

Redis 4.0 引入了混合持久化:AOF 重写时,先以 RDB 格式写入全量数据,再追加增量 AOF 命令。兼顾了 RDB 的快速恢复和 AOF 的数据安全。

混合持久化 AOF 文件结构 appendonly.aof 文件名不变,但内容分两段 前半段:RDB 格式(二进制快照) 以 REDIS 魔数开头,加载速度极快 后半段:AOF 增量命令 重写期间新产生的写命令(RESP 格式) 恢复时先加载 RDB 部分(快),再重放 AOF 增量(补最新数据)
图 4-1 混合持久化:RDB 快照 + AOF 增量命令,两全其美
开启混合持久化
# redis.conf
aof-use-rdb-preamble yes  # Redis 5.0 默认开启

# 同时建议开启 AOF + RDB 双持久化
appendonly yes
save 900 1
save 300 10

# 恢复优先级:AOF > RDB
# 如果 AOF 存在,Redis 优先加载 AOF;AOF 不存在才加载 RDB
关键结论

生产环境最佳实践:RDB + AOF(混合持久化)同时开启。RDB 用于快速备份和恢复,AOF 保证数据不丢。Redis 4.0+ 的混合持久化让 AOF 重写后体积更小、恢复更快。

第 5 站

Sentinel 哨兵——自动故障转移

Redis 主从复制(Master-Slave)解决了数据冗余,但主节点宕机时需要手动切换。Sentinel 实现了自动故障检测和故障转移

Sentinel 哨兵模式架构 Master (6379) 读写 Slave-1 (6380) Slave-2 (6381) 复制 复制 Sentinel-1 Sentinel-2 Sentinel-3 3 个 Sentinel 监控 Master,主观下线需 quorum(默认 2)个 Sentinel 同意 Master 宕机 → Sentinel 选举 Leader → 选一个 Slave 提升为 Master → 通知客户端
图 5-1 Sentinel 哨兵模式:3 个哨兵节点监控主从,自动故障转移
故障转移流程
1. 监控:Sentinel 每秒向 Master/Slave 发送 PING
2. 主观下线(SDOWN):某个 Sentinel 超时未收到回复 → 认为该节点下线
3. 客观下线(ODOWN):quorum 个 Sentinel 都同意 → 确认 Master 下线
4. 选举 Leader Sentinel:Raft 算法选出一个 Sentinel 执行故障转移
5. 选新 Master:从 Slave 中选一个(优先级 > 复制偏移量 > runid)
6. 切换:其他 Slave 复制新 Master,旧 Master 恢复后变 Slave
7. 通知:客户端通过 Sentinel 获取新 Master 地址
Sentinel 配置示例
# sentinel.conf
port 26379

# 监控的 Master 名称、地址、quorum(最少几个 Sentinel 同意才算下线)
sentinel monitor mymaster 192.168.1.100 6379 2

# 主观下线超时(毫秒):超过这个时间没回复就认为下线
sentinel down-after-milliseconds mymaster 5000

# 故障转移超时(毫秒):整个故障转移过程的最大时间
sentinel failover-timeout mymaster 60000

# 故障转移时最多几个 Slave 同时切换 Master
sentinel parallel-syncs mymaster 1
客户端连接 Sentinel 获取 Master
// Jedis Sentinel 示例
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.1.1:26379");
sentinels.add("192.168.1.2:26379");
sentinels.add("192.168.1.3:26379");

JedisSentinelPool pool = new JedisSentinelPool(
    "mymaster",       // 监控的 Master 名称
    sentinels,       // Sentinel 节点列表
    jedisPoolConfig, // 连接池配置
    "password"       // Redis 密码
);

// 自动获取当前 Master 连接(故障转移后自动切换)
try (Jedis jedis = pool.getResource()) {
    jedis.set("key", "value");
}

Sentinel 模式有什么局限性?

Sentinel 解决了高可用问题,但没有解决数据分片问题——所有数据都存在一台 Master 上,内存上限受限于单机。写操作也无法并行。要水平扩展就需要 Cluster 集群

第 6 站

Cluster 分片集群——数据分片 + 高可用

Redis Cluster 是官方提供的分布式解决方案:数据按 slot 分片存储到多个 Master 节点,每个 Master 可以有 Slave 做高可用。

Redis Cluster 架构(3 主 3 从) 16384 个 hash slot 分配到 3 个 Master Master-A Slot 0 ~ 5460 CRC16(key) % 16384 ∈ [0,5460] Master-B Slot 5461 ~ 10922 CRC16(key) % 16384 ∈ [5461,10922] Master-C Slot 10923 ~ 16383 CRC16(key) % 16384 ∈ [10923,16383] Slave-A'(复制 Master-A) Slave-B'(复制 Master-B) Slave-C'(复制 Master-C) 节点间通过 Gossip 协议交换状态信息 PING/PONG 心跳 | 客户端收到 MOVED/ASK 重定向到正确的节点
图 6-1 Redis Cluster:3 主 3 从,16384 slot 分片
数据路由:CRC16 + MOVED 重定向
客户端写入 key="user:1001"
1. 计算 slot = CRC16("user:1001") % 16384 = 7842
2. slot 7842 属于 Master-B(5461~109223. 如果客户端连的是 Master-A → 收到 MOVED 7842 192.168.1.2:6379
4. 客户端重定向到 Master-B 执行命令

# 批量操作的 key 必须落在同一 slot → 使用 hash tag
SET {user}:1001:name  "Alice"
SET {user}:1001:age   25
# {} 内的部分参与 CRC16 计算 → 保证同 slot
关键结论

Cluster 将 16384 个 slot 分给多个 Master,key 通过 CRC16 定位 slot。最少 3 主 3 从部署。支持自动故障转移(类似 Sentinel 但内置于 Cluster)。不适合跨 slot 的批量操作(需要 hash tag 或 Lua 脚本)。

第 7 站

高可用架构选型总结

架构数据分片高可用适用场景复杂度
单机开发测试
主从复制手动切换读多写少
Sentinel 哨兵自动故障转移数据量不大,需高可用
Cluster 集群16384 slot自动故障转移大数据量,高并发读写
持久化 + 高可用 最佳实践 持久化: RDB + AOF(混合持久化)同时开启 高可用: 生产环境至少 3 主 3 从 Cluster 或 1 主 2 从 + 3 哨兵 内存: maxmemory + 淘汰策略(volatile-lru / allkeys-lru) 监控: INFO 命令 + Redis Exporter + Prometheus + Grafana
图 7-1 Redis 生产环境配置清单
淘汰策略淘汰范围淘汰算法适用场景
volatile-lru设置了 TTL 的 key最近最少使用缓存场景(推荐)
allkeys-lru所有 key最近最少使用纯缓存,所有 key 都可淘汰
volatile-lfu设置了 TTL 的 key最不经常使用Redis 4.0+,热点数据场景
allkeys-lfu所有 key最不经常使用Redis 4.0+,访问频率差异大
volatile-random设置了 TTL 的 key随机对淘汰无特殊要求
volatile-ttl设置了 TTL 的 keyTTL 最短的优先快过期的先淘汰
noeviction不淘汰内存满时拒绝写入(默认)
慢查询分析与优化
# 慢查询配置
slowlog-log-slower-than 10000  # 超过 10ms 记录(微秒)
slowlog-max-len 128            # 最多保留 128 条慢查询

# 查看慢查询
SLOWLOG GET 10    # 最近 10 条慢查询
SLOWLOG LEN       # 慢查询总数

# 常见慢查询原因及优化:
# 1. KEYS * → 改用 SCAN(渐进式遍历,不阻塞)
# 2. HGETALL 大 Hash → 改用 HMGET 取需要的 field
# 3. SMEMBERS 大 Set → 改用 SSCAN
# 4. 大 Key 操作 → 拆分成多个小 Key
# 5. FLUSHALL/FLUSHDB → 改用 UNLINK(异步删除,Redis 4.0+)

全篇回顾

  1. 单线程快的原因:内存操作 + epoll 多路复用 + 无锁竞争(Redis 6.0 多线程只用于网络 IO)
  2. RDB:fork 子进程写快照,恢复快但可能丢数据(两次快照之间的写入)
  3. AOF:追加写命令,数据安全但文件大、恢复慢。everysec 是最佳策略
  4. 混合持久化:AOF 重写时 RDB + AOF 增量,兼具两者优点
  5. Sentinel:自动故障转移,但不支持数据分片
  6. Cluster:16384 slot 分片 + 自动故障转移,适合大数据量场景
  7. 选型:小数据 Sentinel,大数据 Cluster,持久化一律开 RDB + AOF