Redis 把数据放在内存里换来了速度,但也带来两个绕不开的问题:进程重启/宕机后数据还在吗(持久化)、单个节点挂了服务还在吗(高可用)。这两个问题的答案,决定了你敢把多少业务押在 Redis 上——是当「挂了回源重建就行的纯缓存」,还是当「不能丢、不能停的在线数据源」。
本篇把这两条线讲透:先是 RDB / AOF / 混合三种持久化,再是主从复制 → Sentinel → Cluster 的高可用三级跳。
Redis 深挖三篇:本篇是第二篇「持久化与高可用」,另外两篇是 数据结构与内存模型 与 线程模型与性能,都是 缓存系列开篇 的下钻。本篇的容灾/多活视角与 冗余与容灾 一脉相承。
TL;DR
- RDB:定时把内存快照成紧凑二进制
dump.rdb。BGSAVEfork子进程 + 写时复制(COW);优点是文件小、恢复最快,缺点是两次快照间宕机丢数据窗口大、写多时 COW 复制页导致内存近翻倍与延迟抖动。 - AOF:把写命令追加到文件。
appendfsync三档:always(每条刷盘,最安全最慢)/everysec(每秒,默认,最多丢 ~1s)/no(交给 OS)。文件会膨胀,靠bgrewriteaof重写压缩。 - 混合持久化(
aof-use-rdb-preamble,4.0+ 默认):AOF 重写时先写 RDB 格式的「头」(全量),再追加增量写命令的「尾」——兼顾体积小/恢复快与丢数据窗口小。重启优先加载 AOF。 - 主从复制:
PSYNC全量(RDB + 复制积压缓冲)+ 增量(repl_backlog);异步复制,故障切换可能丢「已回客户端但没复制到从」的写。 - Sentinel:监控 + 自动故障转移。主观下线
sdown→ 达quorum客观下线odown→ 选举 leader → 提升最优从为新主 → 通知客户端。Sentinel 建议 ≥3 且奇数。 - Cluster:数据分片到 16384 个槽,
slot = CRC16(key) % 16384;客户端按槽直连,落错节点返回 MOVED(永久迁移)/ ASK(迁移中临时)。每主一从 + 自动 failover。 - 跨槽限制:多 key 操作 / 事务 / Lua 要求 key 同槽,用 hash tag
{...}把相关 key 绑到同槽。 - 选型:单机(缓存、能重建)→ 主从+Sentinel(要 HA、数据量单机放得下)→ Cluster(数据量/写吞吐超单机)。
- Redis 偏 AP:故障切换优先可用性,存在丢数据窗口;要强持久用
AOF always+ 合理拓扑,但吞吐会降。
Table of contents
Open Table of contents
1. 先分清:Redis 是「缓存」还是「数据源」
持久化要做到什么程度,取决于你怎么定位 Redis:
- 当纯缓存:数据在别处(DB)有权威副本,Redis 挂了回源重建即可。这时持久化只是为了「重启后不冷启动」,RDB 甚至可以关掉。
- 当在线数据源:某些数据(会话、计数、排行榜、去重集)只在 Redis 里,丢了就真丢了。这时持久化 + 高可用都要认真做。
这个定位决定了后面所有取舍的松紧。
2. RDB:快照
RDB 是某一时刻内存的全量快照,序列化成紧凑二进制文件 dump.rdb。
RDB 靠 fork+COW 做快照、文件小恢复快但丢数据窗口大;AOF 追加写命令、按 appendfsync 三档取舍安全与性能;混合持久化取两者之长。
SAVE:主线程同步生成快照——会阻塞,生产禁用。BGSAVE:fork一个子进程去写快照,主进程继续服务。靠 写时复制(COW):fork 瞬间父子共享内存页,父进程后续对某页有写才复制该页。- 触发:
save <seconds> <changes>配置(如「900s 内 ≥1 次改」)、手动BGSAVE、主从全量同步时。
代价:
- COW 放大:如果快照期间写很多,大量页被复制,内存可能接近翻倍,还会因缺页复制产生延迟抖动(fork 本身在大内存实例上也有停顿)。
- 丢数据窗口大:两次快照之间宕机,这段时间的写全丢。
优点:文件紧凑、加载恢复最快,适合做定期备份、灾备传输、主从全量同步的载体。
3. AOF:命令追加
AOF(Append Only File)记录每一条写命令,重启时重放这些命令重建数据。
写入路径:写命令 → 追加到 aof_buf 内存缓冲 → 按 appendfsync 策略刷盘到 appendonly.aof:
appendfsync | 行为 | 丢数据 | 性能 |
|---|---|---|---|
always | 每条写命令都 fsync | 几乎不丢 | 最慢 |
everysec(默认) | 每秒 fsync 一次 | 最多丢 ~1s | 均衡 |
no | 交给 OS 决定何时刷 | 丢较多 | 最快 |
AOF 重写(bgrewriteaof):AOF 会越追越大(对同一 key 反复写会留下一堆历史命令)。重写会 fork 子进程,用「当前内存状态」生成一份等价但更精简的 AOF(比如 100 次 INCR 压成一条 SET),替换旧文件。重写期间的新写命令先进缓冲区,重写完再追加,保证不丢。
优点:丢数据窗口小、可读性好。缺点:文件比 RDB 大、恢复(重放命令)比加载 RDB 慢、fork 同样有开销。
4. 混合持久化
RDB 恢复快但丢得多,AOF 丢得少但恢复慢——混合持久化(aof-use-rdb-preamble,4.0+ 默认开) 取两者之长:
AOF 重写时,先以 RDB 二进制格式写一个「全量头」,再把重写期间的增量写命令以 AOF 文本格式追加成「尾」。 重启加载时,先快速吃掉 RDB 头恢复大部分数据,再重放少量 AOF 尾补齐最新增量。
于是既有 RDB 的体积小、恢复快,又有 AOF 的丢数据窗口小。生产上常见配置:开 AOF(everysec)+ 开混合持久化 + 保留定期 RDB 备份;重启优先加载 AOF(数据更全)。
5. RDB vs AOF 怎么选
| 维度 | RDB | AOF |
|---|---|---|
| 丢数据窗口 | 大(两次快照间) | 小(≤1s,everysec) |
| 文件体积 | 小 | 大(重写后可控) |
| 恢复速度 | 快 | 慢(重放命令) |
| 对写入的影响 | fork + COW 抖动 | 每秒/每条 fsync |
| 适合 | 备份、灾备、能容忍丢一段 | 不能丢、要点对点最新 |
结论:纯缓存可只留 RDB 甚至关持久化;当数据源用 AOF everysec + 混合持久化 + 定期 RDB 备份。要极致不丢用 always,但要接受吞吐下降。持久化不等于高可用——磁盘上数据还在,但进程挂了业务照样不可用,所以还要往下看主从与 Sentinel/Cluster。
6. 主从复制
高可用的第一步是别把鸡蛋放一个篮子:一主多从,主写从读,主挂了从能顶上。
- 全量同步(
PSYNC):从库首次连接(或断连太久)时,主库BGSAVE生成 RDB 发给从库加载,其间的写命令缓冲在复制积压缓冲区repl_backlog里,加载完再补发。 - 增量同步:网络短暂抖动重连时,若从库的复制偏移量(offset)还在
repl_backlog覆盖范围内,只补发缺失的那段命令,避免昂贵的全量。 - 标识:主库
runID+ 复制offset用来判断能否走增量。 - 级联复制:从库还能挂从库,分摊主库的复制压力。
关键一致性事实:Redis 复制是异步的。 主库把写返回客户端后才异步发给从库。所以主库突然宕机,那些「已回客户端但还没到从库」的写会在故障切换后丢失。WAIT numreplicas timeout 能让写等待若干从库确认,但只是尽力,不是强一致。
7. Sentinel:自动故障转移
主从解决了「有备份」,但主挂了谁来切、怎么切、客户端怎么知道新主?这就是 Sentinel(哨兵)。
Sentinel 监控主从与彼此;主观下线(单个 Sentinel 判定)达 quorum 变客观下线,选举 leader 执行故障转移,最后通过订阅通知客户端新主地址。
流程拆解:
- 监控:每个 Sentinel 每秒 PING 主/从/其他 Sentinel。
- 主观下线
sdown:某个 Sentinel 发现主库 PING 超时超过down-after-milliseconds,标记它「我觉得它挂了」。 - 客观下线
odown:Sentinel 之间互相询问(is-master-down-by-addr),当认为主库挂了的 Sentinel 数达到quorum,判定「大家都觉得它挂了」。 - 选举:Sentinel 用类 Raft 的方式选出一个 leader 来主导本次故障转移。
- 提升新主:leader 从从库里挑**复制偏移量最大(数据最全)**的那个,
REPLICAOF NO ONE让它变主,其余从库改指向新主。 - 通知客户端:Sentinel 发布
+switch-master事件,订阅了的客户端拿到新主地址并重连。
Sentinel 数量要 ≥3 且为奇数,且分布在不同故障域——否则脑裂或选不出 leader。故障转移需要时间(探测超时 + 选举 + 切换),这段窗口客户端可能读写失败,要靠客户端重试 + 本地缓存兜底(见 §10)。
8. Redis Cluster:分片 + 高可用
主从+Sentinel 解决了 HA,但所有数据仍在一个主库里——单机内存/写吞吐是天花板。Cluster 通过分片横向扩展,同时自带故障转移。
数据分 16384 槽、每主一从;客户端按槽直连,落错返回 MOVED(永久迁移)或 ASK(迁移中临时),节点间 gossip 传播拓扑与故障。
- 槽分片:整个键空间分成 16384 个 hash slot,每个主节点负责一段。
slot = CRC16(key) % 16384。 - 客户端路由:集群感知的客户端缓存一份「槽 → 节点」表,直接连对的节点。
- MOVED / ASK 重定向:
- MOVED:槽已永久迁到别的节点 → 客户端更新本地槽表并重连正确节点。
- ASK:槽正在迁移中的临时重定向 → 只针对这一次请求跟去目标节点,不更新槽表。
- gossip 协议:节点间心跳互探,传播拓扑变化与故障信息(不需要中心化的配置服务)。
- 自动 failover:每主配从,主挂了其从被提升为新主(机制类似 Sentinel,但内建在集群里)。
- 扩缩容 = reshard:逐槽把 key 从老节点搬到新节点,迁移期间对迁移中的槽走 ASK。
跨槽限制(最常踩):多 key 操作、事务、Lua 脚本要求所有涉及的 key 落在同一个槽,否则报 CROSSSLOT。解法是 hash tag:给相关 key 加同一个 {...} 段,如 user:{123}:profile 和 user:{123}:budget——只有 {} 内的 123 参与 CRC16,于是同槽。
9. 拓扑选型与跨机房容灾
| 拓扑 | 适用 | 代价 |
|---|---|---|
| 单机 | 纯缓存、数据能重建、量小 | 无 HA、无扩展 |
| 主从 + Sentinel | 要 HA,数据量单机放得下 | 有故障转移窗口;不解决容量/写吞吐 |
| Cluster | 数据量/写吞吐超单机 | 运维复杂、跨槽受限 |
代理方案(Codis、Twemproxy)曾用于在客户端不感知分片时做代理路由,如今多被原生 Cluster 与云托管替代,了解即可。
跨机房 / 容灾:单可用区不够时,把主从跨 AZ 部署(主 AZ-a、从 AZ-b),AZ 故障时切换;跨地域强一致的多活很难(异步复制 + 网络延迟),社区版一般做「一地写、异地灾备只读」,真要 Active-Active 多写常上企业版的 CRDT 方案。这块的 RTO/RPO、同城异地多活权衡,见 冗余与容灾 与 高可用系统设计。
10. AdTech 落地:竞价的 Redis HA
广告竞价把预算快照、频控计数、campaign 元数据放 Redis,读写都在 tmax(80–150ms)关键路径上,对「Redis 抖一下」零容忍。所以 HA 拓扑通常是 Cluster(按 campaign/user 分片)+ 每主一从 + 跨 AZ,且客户端侧一定叠加 L1 本地缓存兜底(见 本地缓存深挖)。
实践建议:
- 关键路径加 L1 本地缓存兜底,Redis 抖动时用略旧快照继续出价,好过 no-bid。
- 缩短 Sentinel 故障发现窗口;客户端配故障转移感知、退避重试与读写分离降级。
- 别把可用性全押在 Redis 切换速度上——故障转移窗口客观存在,业务侧必须有兜底。
参考
- Redis 官方文档 · Persistence:https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/
- Redis 官方文档 · Replication:https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
- Redis 官方文档 · High availability with Sentinel:https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/
- Redis 官方文档 · Cluster specification:https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/
- 黄健宏《Redis 设计与实现》:https://redisbook.com/