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 与写时复制:

  1. fork 动作本身在主线程执行,要复制页表。数据集越大页表越长,fork 耗时从几毫秒到几百毫秒不等,这期间主线程是卡住的,用 latest_fork_usec 观测。
  2. fork 之后父子进程共享物理内存页;此后任何一页被修改,内核先把这页复制一份再改,即写时复制(Copy-on-Write)。快照期间写入越猛,被复制的页越多,极端情况下额外内存接近翻倍。
  3. 透明大页(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 链路,持久化方案必须和复制拓扑一起设计,主节点关持久化就要关自动重启。