Redis持久化机制详解
Redis 的数据活在内存里,进程一没就清零。RDB 快照、AOF 日志与二者的混合模式,回答的是同一个问题:宕机之后用多小的丢失窗口、多快的速度把数据找回来。
它解决什么问题
持久化要兼顾两个互相拉扯的目标:数据安全和运行性能。缓存场景丢几分钟数据无所谓,可以弱持久甚至关掉;把 Redis 当主存储用时,丢失窗口(RPO)和恢复时长(RTO)就是必须写进 SLA 的硬指标。此外 RDB 文件还身兼主从全量同步的搬运工,持久化策略从来不是孤立配置的。
核心原理
RDB:时间点快照
RDB 把某一时刻的整个数据集序列化成压缩的二进制文件(默认 dump.rdb),适合备份与灾难恢复。触发方式有两个命令:
save:主线程亲自遍历内存写盘,写完之前所有客户端请求排队,生产环境禁用。bgsave:主线程 fork 出子进程,由子进程写 RDB,主线程继续处理命令。配置文件里的save 3600 1 300 100 60 10000这类自动规则,底层走的也是 bgsave。
bgsave 不阻塞服务,但不等于零成本,关键在于 fork 与写时复制:
- fork 动作本身在主线程执行,要复制页表。数据集越大页表越长,fork 耗时从几毫秒到几百毫秒不等,这期间主线程是卡住的,用
latest_fork_usec观测。 - fork 之后父子进程共享物理内存页;此后任何一页被修改,内核先把这页复制一份再改,即写时复制(Copy-on-Write)。快照期间写入越猛,被复制的页越多,极端情况下额外内存接近翻倍。
- 透明大页(THP)会把复制粒度从 4KB 放大到 2MB,改 10 个字节可能复制整页大页,COW 开销瞬间失控,必须关闭。
AOF:逐条追加的写命令日志
AOF 的思路完全不同:把每条改变数据的命令按协议文本追加到文件,恢复时重放一遍。流程是命令先执行、再记入 aof_buf、write 进内核缓冲,最后按 appendfsync 策略决定何时 fsync 落盘。先执行后记录的好处是不做语法校验、不挡当前命令,代价是刚执行完就宕机时这条命令会丢。
三种 fsync 策略对应三档安全级别:
always:主线程写完立即 fsync,最多丢一个事件循环里的命令,吞吐直接被磁盘延迟锁死,一般不用。everysec:后台线程每秒 fsync 一次,正常最多丢 1 秒数据,性能几乎无损,是主流选择。注意磁盘繁忙时上一次 fsync 超过 2 秒未完成,主线程下一次 write 会被迫等待,丢失窗口拉长到 2 秒并伴随延迟毛刺,用aof_delayed_fsync指标监控。no:只 write 不主动 fsync,落盘时机交给内核(通常 30 秒级),性能最好,宕机丢多少完全不可控。
文件只增不减会无限膨胀,所以要有重写(rewrite):fork 子进程扫描当前内存数据,直接生成能重建现状的最小命令序列(每个 key 只留一条最终态命令),与旧 AOF 的历史内容无关。重写期间新来的写命令同时进旧文件缓冲和重写缓冲区,子进程完工后把重写缓冲追加到新文件末尾,再原子替换。触发由 auto-aof-rewrite-percentage(相对上次重写后体积的涨幅,默认 100%)和 auto-aof-rewrite-min-size(默认 64MB 门槛)共同控制。7.0 起改为 Multi-Part AOF:base 全量文件加 incr 增量文件加 manifest 清单,重写期间增量直接写新的 incr 文件,消掉了旧版的重写缓冲内存与双写开销。
混合持久化(4.0+)
开启 aof-use-rdb-preamble yes(7.0 起默认开)后,AOF 重写时前半段直接写 RDB 格式的二进制快照,后半段照旧追加增量命令。恢复时先高速装载 RDB 部分,再重放尾部增量,兼得 RDB 的恢复速度和 AOF 的低丢失窗口。代价是文件前半段不可读,且重写要多花 RDB 序列化的 CPU。
宕机重启的加载顺序很明确:开了 AOF 就优先用 AOF 重建数据集,因为它通常更完整;只有没开 AOF 时才去找 RDB 文件。
深入机制:持久化与主从复制
RDB 文件同时是主从全量同步的载体。从节点首次接入(或断线太久无法增量追平)时,主节点 bgsave 生成 RDB 传给从节点,传输期间的新写命令堆在复制缓冲区,传完再追平。这意味着 fork 的代价不只在持久化路径上——主从扩容、全量重传都会踩到,大数据集实例必须给 fork 预留内存余量。常见部署是把 bgsave 和 AOF 挪到从节点执行,主节点只做轻量持久化甚至关闭;但此时主节点绝不能配崩溃自动拉起,否则它以空数据集重启后,会把空库同步给所有从节点,造成数据清零事故。
工程实践
- 纯缓存:关掉持久化或只留低频 RDB 冷备;主存储用途:AOF everysec 加混合持久化,RDB 定期归档到对象存储。
- 关掉 THP(
echo never > /sys/kernel/mm/transparent_hugepage/enabled),设置vm.overcommit_memory=1防止 fork 因内存评估失败。 - 监控四件套:
rdb_last_bgsave_time_sec、rdb_last_cow_size、aof_delayed_fsync、数据盘水位(建议低于 70%,重写要临时生成新文件)。 - AOF 文件损坏用
redis-check-aof --fix截断修复(丢截断点之后的数据),RDB 只能检测不能修,靠备份兜底。 - 定期演练恢复流程。没真正恢复过的备份,等于没有备份。
对比与权衡
| 维度 | RDB | AOF(everysec) | 混合持久化 |
|---|---|---|---|
| 丢失窗口 | 上次快照至今,分钟级 | 通常不超过 1 秒 | 不超过 1 秒 |
| 恢复速度 | 快,直接装载 | 慢,逐条重放 | 快,RDB 打底加增量重放 |
| 文件体积 | 小,压缩二进制 | 大,文本日志 | 中等 |
| 运行时开销 | 周期性 fork 与写盘 | 每条命令追加,每秒 fsync | 同 AOF |
| 可读可修复性 | 差 | 好,文本可手工编辑 | 快照段不可读 |
选型一句话:纯缓存可裸奔,要安全就开 AOF everysec,4.0 以上务必叠加混合持久化,RDB 留作冷备。
面试视角
bgsave 为什么还会造成卡顿? 两处:fork 本身在主线程执行,要复制页表,数据集越大越慢;fork 之后依赖写时复制,快照期间写入越猛复制的页越多,内存额外开销越大,THP 还会把复制粒度从 4KB 放大到 2MB。所以要看 latest_fork_usec,并关闭 THP。
AOF everysec 真的只丢 1 秒吗? 理想情况是。磁盘 I/O 繁忙导致后台 fsync 超过 2 秒没完成时,主线程写命令会被阻塞等待,此时宕机最多丢约 2 秒,且客户端感知为延迟飙升,看 aof_delayed_fsync 判断。
AOF 重写会丢数据吗? 不会。重写期间新命令同时写旧文件缓冲和重写缓冲区,子进程生成新文件后把重写缓冲补齐,再原子替换,新旧文件覆盖同一状态。
RDB 和 AOF 同时开启,重启加载哪个? 优先 AOF,因为按命令重放通常比 RDB 快照更完整;只有 AOF 未开启时才加载 RDB。
小结
- RDB 是紧凑的时间点快照,恢复快、适合备份,丢失窗口以分钟计,成本在 fork 与写时复制。
- AOF 逐条记录写命令,everysec 在性能与约 1 秒丢失之间取得平衡,重写机制负责压缩体积,7.0 的 Multi-Part AOF 进一步消掉重写期开销。
- 混合持久化让 AOF 重写带上 RDB 前缀,恢复速度与完整性兼得,是 4.0 以上的生产标配。
- 主从全量同步复用 RDB 链路,持久化方案必须和复制拓扑一起设计,主节点关持久化就要关自动重启。

