把 Redis 持久化讲透:RDB、AOF、重写、恢复与生产选型

6 阅读18分钟

很多人知道 Redis 有两套持久化方案:RDBAOF

但一到细节就开始混:

  • 为什么 RDB 叫“快照”,它到底什么时候生成?
  • 为什么生产里几乎不用 SAVE,而是用 BGSAVE
  • 为什么 AOF 更安全,却又更容易变大?
  • BGREWRITEAOF 到底在“重写”什么?是改旧文件,还是生成新文件?
  • 为什么很多线上环境会同时开 RDB + AOF,甚至再配“混合持久化”?

这篇文章不只讲概念,而是把 Redis 持久化背后的运行机制、数据恢复流程和配置取舍讲透。

一、Redis 为什么一定要讲持久化

Redis 本质上是内存数据库

数据主要存在内存里,读写速度非常快,但这也意味着一个直接问题:

如果进程退出、机器重启、容器被销毁,纯内存数据会全部丢失。

所以 Redis 要解决的不是“怎么把数据放进内存”,而是:

  1. 服务重启后怎么把数据恢复回来
  2. 宕机时最多能接受丢多少数据
  3. 持久化带来的 CPU、磁盘、内存开销能不能接受
  4. 备份、迁移、主从同步时如何复用持久化文件

Redis 围绕这个问题,提供了两条路线:

  • RDB:在某个时间点,把当前内存数据做成一个快照
  • AOF:把每条会修改数据的写命令,按顺序追加记录下来

先记住一句话:

RDB 关注“某一刻的数据长什么样”,AOF 关注“数据是如何一步步变成现在这个样子的”。


二、先建立一个总模型

可以把 Redis 持久化理解成两种不同的“存档思路”。

1. RDB:拍照片

某个时刻,Redis 把内存中的整个数据集序列化到磁盘,生成一个二进制文件,比如 dump.rdb

它记录的是:

“这一刻数据库的完整状态。”

所以 RDB 的恢复方式也很直接:

把这张快照重新读回内存。

2. AOF:记流水账

Redis 每执行一条写命令,比如:

SET name redis
INCR counter
HSET user:1 name zhangsan age 20

就把这些命令按协议格式追加到 AOF 文件里。

它记录的是:

“为了得到现在的数据,系统执行过哪些写操作。”

所以 AOF 的恢复方式是:

从头到尾重放这些写命令,重新构建数据集。

3. 两者不是互斥,而是互补

很多人一开始会问:

“到底该选 RDB 还是 AOF?”

真实生产里更常见的答案是:

不是二选一,而是按目标组合使用。

  • 想要恢复快、文件紧凑、便于备份:RDB 很有价值
  • 想要更小的数据丢失窗口:AOF 更有优势
  • 想同时兼顾恢复速度和数据安全:通常会启用 AOF + RDB,再配混合持久化

三、RDB 机制:Redis 的“数据快照”是怎么来的

1. 什么是 RDB

RDB,Redis Database Backup,本质上就是 Redis 在某个时间点把内存数据序列化成一个紧凑的二进制文件。

常见文件名是:

dump.rdb

这个文件里存的是键值数据、过期时间、编码信息等,目的是让 Redis 能在之后直接把数据恢复回来。

RDB 最大的特点是:

它保存的是结果,不保存过程。

例如现在 Redis 里有一个键:

counter = 1000000

RDB 并不关心你之前到底执行过一百万次 INCR,它只关心“当前这个键的值是 1000000”。


四、RDB 何时触发

RDB 的生成有两种主要方式。

1. 手动触发

Redis 提供两个常见命令:

SAVE
BGSAVE

它们都能生成 RDB,但行为差异非常大。

SAVE

SAVE 会由 Redis 主进程直接执行持久化。

这意味着:

在生成 RDB 期间,Redis 不能继续处理新的客户端请求。

所以它是阻塞式的,线上几乎不会用。

BGSAVE

BGSAVEfork 一个子进程,由子进程负责把当前数据集写入 RDB 文件。

主进程继续处理客户端请求,所以这是生产环境真正常用的方式。

一句话总结:

  • SAVE:阻塞主线程,不适合线上
  • BGSAVE:后台生成快照,生产常用

2. 自动触发

Redis 可以通过配置项按“时间 + 修改次数”自动触发快照,例如:

save 900 1
save 300 10
save 60 10000

这三条规则表示:

  • 900 秒内至少有 1 次写操作,就触发一次快照
  • 300 秒内至少有 10 次写操作,就触发一次快照
  • 60 秒内至少有 10000 次写操作,就触发一次快照

它的本质不是定时器单独起作用,而是:

“时间条件 + 脏数据变更次数条件”同时满足时,触发一次 BGSAVE


五、BGSAVE 的底层流程到底发生了什么

很多人知道 BGSAVE “会 fork 一个子进程”,但真正重要的是后面的细节。

它的核心流程可以抽象成这样:

客户端写入数据
    |
    v
Redis 决定触发 BGSAVE
    |
    v
主进程 fork 子进程
    |
    +-- 子进程:遍历当前内存数据,序列化并写入临时 RDB 文件
    |
    +-- 主进程:继续处理客户端读写请求
    |
    v
子进程写完后,用临时文件替换旧的 RDB 文件

这里最关键的是两个点:forkCopy-On-Write

1. 为什么一定要 fork

如果 Redis 主进程自己去遍历整个内存并写磁盘,那么持久化期间就没法继续提供服务。

fork 出子进程后:

  • 子进程拿到的是生成瞬间的内存视图
  • 主进程继续响应客户端请求

于是 Redis 就能做到:

“快照在后台生成,在线请求继续处理。”

2. Copy-On-Write 是什么

fork 并不意味着立刻把整个内存复制一份。

操作系统会采用 Copy-On-Write,写时复制

  • fork 完时,父子进程共享同一批物理内存页
  • 如果主进程后续只是读取数据,不需要额外复制
  • 只有当主进程修改某些内存页时,操作系统才会把这些被修改的页复制出来

所以 BGSAVE 的代价不是“瞬间复制整个 Redis 内存”,而是:

  1. fork 本身有一次页表复制和暂停成本
  2. 持久化期间如果写流量很大,会触发更多内存页复制,造成额外内存开销

这也是为什么线上做 RDB 时,通常要关注两个风险:

  • fork 耗时:实例很大时,fork 可能明显卡顿
  • 内存膨胀:快照期间写入越多,Copy-On-Write 额外占用越大

六、RDB 文件生成完成后如何恢复

Redis 启动时如果发现有可用的 RDB 文件,会读取这个文件,把里面的数据重新加载到内存。

它的恢复过程可以理解为:

读取 RDB 文件
    |
    v
解析文件头、版本、校验信息
    |
    v
逐个反序列化 key / value / expire time
    |
    v
重建内存中的数据结构

因为 RDB 记录的是某一刻的完整结果,所以恢复时不需要像 AOF 那样重放大量命令。

这带来一个很重要的优点:

RDB 恢复通常比 AOF 更快。


七、RDB 的优点和代价

优点

1. 文件紧凑,适合备份

RDB 是压缩后的二进制快照,通常比 AOF 更小,更适合做:

  • 冷备份
  • 数据归档
  • 机器迁移
  • 灾难恢复

2. 恢复速度通常更快

因为它直接加载结果,不需要逐条执行写命令。

3. 对运行时写入路径影响相对可控

RDB 不是每次写都落盘,而是按周期生成快照,所以平时写请求路径比较短。

代价

1. 会丢失两次快照之间的数据

如果上一次 RDB 是 10:00,下一次本来打算 10:05 生成,但 10:04:59 机器宕机,那么 10:00 之后的数据都可能丢失。

这就是 RDB 的核心短板:

数据丢失窗口通常按“分钟级”甚至更长来算。

2. fork 和 Copy-On-Write 会带来性能与内存压力

实例越大、写流量越高,这个成本越明显。

3. 快照不够“实时”

RDB 更像备份,不像细粒度日志。


八、AOF 机制:Redis 的“操作日志”是怎么工作的

1. 什么是 AOF

AOF,Append Only File,顾名思义就是“只追加文件”。

Redis 执行写命令后,不是立刻把整个数据集重新写一遍,而是把这次写操作按 Redis 协议追加到 AOF 文件末尾。

例如执行:

SET name redis
INCR counter
EXPIRE session:1 300

AOF 里会追加相应的协议内容。这样当 Redis 重启时,只要把这些命令重新执行一遍,数据就能恢复出来。

所以 AOF 的核心思想是:

“把状态变化过程保存下来。”


九、Redis 写入 AOF 时不是“每条命令都立刻刷盘”

很多人第一次接触 AOF,会误以为流程是:

执行一条写命令 -> 立刻写磁盘 -> 立刻 fsync

如果真这么做,性能会非常差。

Redis 实际上的流程更接近:

客户端写命令到来
    |
    v
主线程执行命令,修改内存
    |
    v
把命令内容追加到 AOF 缓冲区
    |
    v
按配置策略写入内核缓冲区并决定何时 fsync 到磁盘

这里最重要的配置是:

appendonly yes
appendfsync always | everysec | no

1. appendfsync always

每次写命令都尽量执行 fsync

优点:

  • 数据最安全

缺点:

  • 吞吐下降明显
  • 延迟抖动可能更大

这个模式一般只在极端强调持久化安全的场景使用。

2. appendfsync everysec

每秒做一次 fsync,这是生产里最常见的折中方案。

它意味着:

理论上最多丢失 1 秒左右的数据。

相比 always,性能要友好很多;相比 no,数据安全又明显更好。

3. appendfsync no

不主动控制 fsync 时机,更多交给操作系统决定什么时候把缓冲区刷到磁盘。

优点:

  • 性能最好

缺点:

  • 数据丢失窗口不可控

所以大多数线上配置里,AOF 常见选择是:

appendonly yes
appendfsync everysec

十、AOF 是如何恢复数据的

Redis 重启时,如果开启了 AOF,通常会优先使用 AOF 恢复数据,因为它往往比 RDB 更新。

恢复流程可以抽象成:

打开 AOF 文件
    |
    v
从头到尾解析 Redis 协议
    |
    v
依次重新执行每一条写命令
    |
    v
把数据重新构建到内存中

这也解释了 AOF 的一个天然特点:

AOF 文件越大、命令越多,启动恢复通常越慢。

因为它需要“重放过程”,而不是直接加载结果。


十一、AOF 为什么会越来越大

假设你对同一个 key 连续执行这些命令:

SET counter 1
INCR counter
INCR counter
INCR counter

最终结果只是:

counter = 4

但 AOF 里可能记了 4 条命令。

如果这种更新长期发生,AOF 会越来越大,原因有三个:

  1. 它记录的是过程,不是最终结果
  2. 同一个 key 被重复修改,会累计很多历史命令
  3. 删除、覆盖、过期等操作本身也要写入日志

所以 AOF 需要一个关键机制:

AOF 重写。


十二、BGREWRITEAOF 到底在重写什么

很多人看到“重写”两个字,第一反应是:

是不是把旧 AOF 文件读出来再压缩一遍?

不是。

Redis 的 AOF 重写,本质上做的是:

根据当前内存里的最终数据状态,重新生成一份更短、更干净的新 AOF。

例如,旧 AOF 里可能有:

SET counter 1
INCR counter
INCR counter
INCR counter

重写后完全可能变成:

SET counter 4

也就是说,AOF 重写不是“日志文件文本压缩”,而是:

“用当前数据集,重建一份能够恢复出同样结果的最小命令集。”


十三、BGREWRITEAOF 的执行流程

BGSAVE 一样,AOF 重写也不能阻塞线上请求,所以 Redis 使用后台子进程完成。

经典流程可以抽象成这样:

主进程收到 BGREWRITEAOF
    |
    v
fork 子进程
    |
    +-- 子进程:根据当前内存数据生成新的临时 AOF 文件
    |
    +-- 主进程:继续处理客户端请求
           |
           +-- 正常把新写命令追加到旧 AOF
           +-- 同时把重写期间的新写命令记录到重写缓冲区
    |
    v
子进程完成新 AOF 文件
    |
    v
主进程把重写缓冲区里的增量命令追加到新 AOF
    |
    v
用新 AOF 原子替换旧 AOF

这里有两个关键点必须理解。

1. 子进程写的是“新文件”,不是改“旧文件”

如果直接在旧 AOF 上原地改写,会很难保证:

  • 文件一致性
  • 线上继续写入时的正确性
  • 失败后的可恢复性

所以 Redis 的策略始终是:

先生成新临时文件,成功后再替换旧文件。

这是很多高可靠系统都爱用的套路。

2. 为什么主进程还要维护一个“重写缓冲区”

因为子进程开始重写时,它看到的是 fork 那一刻的内存快照。

但重写期间,主进程还在继续接收新的写请求。

如果这些新增写入不额外记录下来,就会出现问题:

  • 新 AOF 只反映了 fork 时刻的数据
  • fork 之后发生的新写操作会丢失

所以 Redis 的做法是:

  • 主进程正常服务
  • 新写命令继续写入旧 AOF,保证持久化不中断
  • 同时把这段增量写入记录到 AOF 重写缓冲区
  • 等子进程的新文件写完,再把这段增量补到新文件末尾

这样替换之后,新 AOF 才是完整的。


十四、自动 AOF 重写是如何触发的

Redis 可以根据 AOF 文件膨胀比例自动触发重写,常见配置如下:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

它的含义可以简单理解为:

  • AOF 文件至少达到 64MB
  • 且当前大小相对上一次重写后膨胀了 100%

满足条件后,Redis 会在后台触发一次 BGREWRITEAOF

这样做的目的是避免 AOF 无限增长。


十五、AOF 的优点和代价

优点

1. 数据安全性通常比 RDB 更好

尤其在 appendfsync everysec 下,最多通常只会丢 1 秒左右的数据。

2. 日志语义更清晰

AOF 记录的是写命令序列,所以从原理上更接近数据库的 WAL 思想。

3. 可读性比 RDB 更强

AOF 本质上是协议化命令流,理解和排查通常比纯二进制快照更直观。

代价

1. 文件通常更大

因为它记过程,天然比只记结果更容易膨胀。

2. 恢复通常更慢

因为需要重放写命令。

3. 运行期开销更持续

每次写请求都要走 AOF 追加路径,并按策略刷盘。

4. 重写期间同样有 fork 和 Copy-On-Write 成本

这点和 BGSAVE 类似。


十六、RDB 和 AOF 到底怎么选

先给结论,再看原因。

维度RDBAOF
持久化方式周期性快照追加写命令日志
数据丢失窗口通常更大通常更小
文件体积更紧凑往往更大
恢复速度通常更快通常更慢
对运行时写路径影响平时较小,集中在快照时每次写都有追加成本
典型用途备份、迁移、快速恢复提高数据安全性

什么时候更偏向 RDB

  • Redis 更像缓存,数据可从别处重建
  • 更关注恢复速度和备份文件体积
  • 能接受分钟级数据丢失

什么时候更偏向 AOF

  • Redis 承担重要业务数据
  • 更关注宕机时的数据安全
  • 能接受一定的磁盘和写入性能成本

生产里更常见的做法

开启 AOF,同时保留 RDB。

原因很简单:

  • AOF 提供更小的数据丢失窗口
  • RDB 提供更快的冷恢复和更适合备份的快照文件

如果两者都开启,Redis 启动恢复时通常会优先使用 AOF,因为它通常更“新”。


十七、为什么还会有“混合持久化”

如果你看到配置:

aof-use-rdb-preamble yes

这就是常说的混合持久化思路。

它解决的问题很实际:

  • 纯 AOF 恢复慢
  • 纯 RDB 丢数据窗口大

混合持久化的核心做法是:

在 AOF 重写时,前半段直接用 RDB 方式写入当前数据快照,后半段再追加增量 AOF 命令。

你可以把它理解成:

新 AOF 文件 = 一份紧凑快照 + 快照之后的增量命令

这样做有两个直接好处:

  1. 前半段用类似 RDB 的紧凑格式,文件更小、加载更快
  2. 后半段保留增量命令,仍然能保证较好的数据新鲜度

所以混合持久化本质上是:

把 RDB 的“恢复快”和 AOF 的“丢数据少”尽量组合起来。


十八、把三套机制串起来理解一次

到这里,可以把 Redis 持久化整体串成一个统一模型:

1. 平时写请求到来

  • Redis 先修改内存
  • 如果开启 AOF,则把写命令追加到 AOF 缓冲区
  • 再按 appendfsync 策略决定何时刷盘

2. 到了快照时机

  • Redis 触发 BGSAVE
  • 子进程把当前内存做成 RDB 快照
  • 主进程继续服务请求

3. AOF 太大时

  • Redis 触发 BGREWRITEAOF
  • 子进程按当前数据集生成新的、更短的 AOF
  • 主进程把重写期间的增量写请求补到新文件
  • 最终替换旧 AOF

4. Redis 重启时

  • 如果开启了 AOF,通常优先用 AOF 恢复
  • 否则使用 RDB 恢复
  • 如果启用了混合持久化,恢复时会先加载快照部分,再回放增量命令

十九、生产环境里几个非常实际的注意点

1. 不要在线上手动执行 SAVE

它会阻塞 Redis 主进程,容易把请求延迟直接打爆。线上应优先使用 BGSAVE

2. 评估 fork 成本,不要只看内存大小

实例越大、写流量越高,BGSAVEBGREWRITEAOF 的额外成本越明显。尤其是高写入场景下,Copy-On-Write 可能导致内存峰值上涨。

如果业务写入高峰非常明显,还要尽量避免把快照或 AOF 重写压在同一个峰值窗口里,否则延迟抖动和内存波动都会更难看。

3. appendfsync everysec 往往是更现实的平衡点

always 太重,no 风险太大,everysec 是很多业务场景下更合理的默认值。

4. 持久化不是高可用的替代品

即使开了 AOF 和 RDB,也不代表你拥有完整的高可用能力。

持久化解决的是:

  • 单机重启后的数据恢复
  • 磁盘级别的数据落地

但主从切换、故障自动转移、跨机房容灾,仍然要靠:

  • 主从复制
  • Sentinel
  • Cluster

5. 定期校验和备份持久化文件

RDB 和 AOF 不是“生成了就万事大吉”。

真正线上要考虑:

  • 持久化文件是否能正常恢复
  • 备份链路是否可靠
  • 文件损坏后如何校验和修复

实践里通常会用到类似工具:

  • redis-check-rdb
  • redis-check-aof

二十、最容易混淆的几个点

1. RDB 不是“实时备份”

它是时间点快照,不是连续日志。

2. AOF 不是“每条命令都马上安全落盘”

是否真正刷到磁盘,要看 appendfsync 策略。

3. AOF 重写不是修改旧文件

它是基于当前数据集生成一个新文件,再替换旧文件。

4. BGSAVE / BGREWRITEAOF 都不是“没有成本”

它们虽然不阻塞整个服务流程,但都依赖 fork,而 fork 与 Copy-On-Write 在大实例下都可能带来明显开销。


二十一、最后做一个总结

如果只记一句话,那就是:

RDB 保存的是“某个时刻的数据结果”,AOF 保存的是“达到这个结果的写入过程”。

进一步展开:

  • RDB 像拍快照,恢复快、文件小,但数据丢失窗口更大
  • AOF 像记流水账,数据更安全,但文件更大、恢复更慢
  • BGSAVEBGREWRITEAOF 都依赖 fork + Copy-On-Write
  • AOF 重写不是压缩旧日志,而是用当前数据集重建更短的新日志
  • 真正的生产实践通常不是单选,而是 AOF + RDB 组合,必要时再启用混合持久化

所以在工程上,Redis 持久化从来不是一道“背概念”的题,而是一道标准的权衡题:

你到底更在意恢复速度、磁盘体积、运行时开销,还是宕机时的数据损失窗口?

只有把这几个维度一起放进来,RDB 和 AOF 的取舍才会真正清楚。