一文吃透 Redis 持久化:RDB 快照与 AOF 日志(含 AOF 重写全流程)
为什么要有这篇博客?
Redis 的数据全都放在内存里——内存虽快,却是易失性存储:一旦进程退出、服务器宕机,内存里的数据就会全部丢失。为了解决这个问题,Redis 提供了两种持久化方式,把内存数据保存到硬盘上,下次重启时再加载回来,保证数据的可持久性(Durability)。
目录
- 一、为什么要持久化
- 二、RDB:内存快照
- 2.1 触发机制
- 2.2 bgsave 的运作流程(重点)
- 2.3 RDB 的优缺点
- 三、AOF:写命令日志
- 3.1 AOF 的工作流程
- 3.2 文件同步策略 appendfsync
- 3.3 AOF 的优缺点
- 四、AOF 重写机制(重点)
- 4.1 为什么需要重写
- 4.2 重写的触发方式
- 4.3 重写的完整流程(逐步详解)
- 五、启动时数据恢复
- 六、总结与对比
一、为什么要持久化
Redis 是一个内存键值数据库——数据平时只存在于内存中。内存带来了微秒级的读写速度,这是 Redis 高性能的根基;但内存有一个致命属性:断电即失。
如果没有任何持久化措施:
- Redis 进程崩溃 → 数据全部丢失;
- 服务器断电重启 → 数据全部丢失;
- 想做主从复制、灾难备份 → 无从谈起。
❗一句话总结:内存负责「快」,硬盘负责「不丢」。持久化就是 Redis 把内存数据写到硬盘的过程,Redis 提供了两种策略——RDB(快照)和 AOF(日志)。
二、RDB:内存快照
RDB(Redis Database)持久化是把当前进程中的数据生成快照保存到硬盘的过程,产生的文件是一个紧凑的二进制文件(默认dump.rdb)。
2.1 触发机制
RDB 的触发分为手动触发和自动触发两类。
手动触发对应两个命令:
| 命令 | 行为 | 是否阻塞 |
|---|---|---|
save | 阻塞当前 Redis 服务器,直到 RDB 过程完成为止 | ❌ 全程阻塞,内存大的实例会长时间卡住,基本不用 |
bgsave | Redis 进程执行fork创建子进程,RDB 持久化由子进程负责 | ✅ 只在 fork 阶段短暂阻塞 |
❗ Redis 内部所有涉及 RDB 的操作都采用类似bgsave的方式。
自动触发(实战中真正有价值的部分):
- save 配置:
save m n表示m 秒内数据集发生了 n 次修改,就自动触发 RDB 持久化(如save 900 1); - 从节点做全量复制时,主节点自动执行 RDB 持久化,随后把 RDB 文件内容发送给从节点;
- 执行shutdown命令关闭 Redis 时,会执行一次 RDB 持久化。
2.2 bgsave 的运作流程(重点)
对照上图,bgsave 共分 5 步:
- 执行 bgsave 命令:Redis 父进程判断当前是否存在其他正在运行的子进程(比如正在执行 RDB/AOF),如果存在,bgsave 直接返回,不执行;
- 父进程执行 fork 创建子进程:fork 过程中父进程会阻塞(可通过
info stats查看latest_fork_usec选项获取最近一次 fork 的耗时,单位微秒); - fork 完成后立即放行:bgsave 命令返回
"Background saving started",父进程不再阻塞,继续响应其他命令; - 子进程创建 RDB 文件:子进程根据父进程的内存数据生成临时快照文件(
temp-xxx.rdb),完成后对原有 RDB 文件进行原子替换(可用lastsave命令查看最后一次生成 RDB 的时间); - 子进程向父进程发送信号,表示完成,父进程更新统计信息。
💡为什么子进程能"拍"到父进程内存的快照?
这依赖 Linux 的写时复制(Copy-On-Write,COW)机制:fork 出的子进程与父进程共享同一份物理内存,只有当父进程发生写操作时,才会真正复制被修改的那一页数据。因此子进程拿到的永远是fork 那一刻的干净数据副本,而父进程可以照常接受写入——两边互不干扰。
💡RDB 文件的处理:文件保存在
dir配置指定目录(文件名dbfilename,默认 dump.rdb);Redis 默认采用LZF 算法对 RDB 文件压缩,体积远小于内存数据量;Redis 还会对文件做校验,启动时加载到损坏的 RDB 文件会拒绝启动(可用redis-check-dump工具检测修复)。
2.3 RDB 的优缺点
优点:
- RDB 是一个紧凑压缩的二进制文件,代表某个时间点上的数据快照,非常适合备份、全量复制等场景(比如每 6 小时执行 bgsave,并把文件复制到远程机器);
- Redis 加载 RDB 恢复数据的速度远远快于 AOF。
缺点:
- 没办法做到实时/秒级持久化:bgsave 每次运行都要执行 fork 创建子进程,属于重量级操作,频繁执行成本过高;所以两次快照之间写入的数据,宕机时就丢了;
- RDB 使用特定二进制格式保存,Redis 版本演进过程中有多个 RDB 版本,老版本兼容性可能存在问题。
三、AOF:写命令日志
AOF(Append Only File)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF 文件中的命令,达到恢复数据的目的。
如果说 RDB 是「给内存拍照片」,那 AOF 就是「把每一笔账都记在本子上」——它的主要作用是解决数据持久化的实时性,是目前 Redis 持久化的主流方式。
3.1 AOF 的工作流程
开启 AOF 需要配置appendonly yes(默认不开启),文件名由appendfilename配置(默认appendonly.aof)。
AOF 的工作流程共 4 步:
- 命令写入(append):所有的写入命令会追加到 **aof_buf(缓冲区)**中。AOF 记录的内容是 Redis 文本协议格式,例如
set hello world会追加:*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n; - 文件同步(sync):AOF 缓冲区根据对应的策略向硬盘做同步操作;
- 文件重写(rewrite):随着 AOF 文件越来越大,需要定期对文件进行重写,达到压缩的目的(详见第四章);
- 重启加载(load):当 Redis 服务器启动时,可以加载 AOF 文件进行数据恢复。
❗为什么需要 aof_buf 缓冲区?
Redis 使用单线程响应命令,如果每次写命令都直接同步硬盘,性能就会从「内存读写」降级为「IO 读写」。先写入缓冲区可以有效减少 IO 次数,同时 Redis 还提供多种缓冲区同步策略,让用户在「安全性」和「性能」之间自由权衡。
3.2 文件同步策略 appendfsync
Redis 提供了三种 AOF 缓冲区同步文件策略,由参数appendfsync控制:
| 可配置值 | 含义 | 特点 |
|---|---|---|
always | 命令写入 aof_buf 后立即调用 fsync同步,完成后返回 | 最安全,但性能很差(SATA 硬盘只有几百 TPS),非极重要数据不建议 |
everysec | 命令写入 aof_buf 后只执行 write,每秒由后台线程 fsync 一次 | 默认配置、推荐配置,兼顾安全与性能,最多丢 1 秒数据 |
no | 只执行 write,fsync 频率交给操作系统决定 | 性能最好,但丢失风险大,一般不建议 |
💡write 与 fsync 的区别:
write只是把数据写进内核的页缓冲区就立即返回(宕机时缓冲区数据可能丢失);fsync针对单个文件做强制硬盘同步,会阻塞直到数据真正写到磁盘。
3.3 AOF 的优缺点
优点:
- 实时性远好于 RDB(everysec 策略最多丢 1 秒数据),是目前的主流持久化方式;
- 文本是可读的命令协议,兼容性好、实现简单。
缺点:
- AOF 文件是命令日志,相同的数据往往占据比 RDB 更大的体积;
- 恢复时需要逐条重放命令,加载速度慢于 RDB;
- 文件会随命令不断增大——这就引出了下面的重写机制。
四、AOF 重写机制(重点)
4.1 为什么需要重写
随着命令不断写入 AOF,文件会越来越大。但文件里其实有大量「废话」:
- 对同一个 key 反复修改,只有最后一次是有效的;
del、hdel、srem等删除命令留下的痕迹毫无意义;- 已经过期的数据还躺在文件里;
- 对同一个 list 连续
lpush list a、lpush list b、lpush list c,完全可以合并成一条。
**AOF 重写(rewrite)**就是把这些冗余压缩掉:把 Redis 进程内的当前数据直接转化为最终状态的写命令,同步到一个全新的 AOF 文件,最后用它替换旧文件。重写后的 AOF 体积更小 → 硬盘占用更少 → 启动恢复速度更快。
4.2 重写的触发方式
- 手动触发:调用
bgrewriteaof命令; - 自动触发:由两个参数共同决定:
auto-aof-rewrite-min-size:触发重写时 AOF 的最小文件体积,默认64MB(低于这个大小从不重写);auto-aof-rewrite-percentage:当前 AOF 文件大小相对于上次重写后大小的增长比例,达到比例就自动触发。
4.3 重写的完整流程(逐步详解)
对照上图,重写流程如下:
① 执行 AOF 重写请求:如果当前正在执行 AOF 重写,本次请求不执行;如果正在执行bgsave,重写命令延迟到 bgsave 完成之后再执行;
② 父进程执行 fork 创建子进程;
③ 重写期间,父子进程各干各的(最关键的一步):
- 父进程(3.1):fork 之后继续响应其他命令,所有修改操作照常写入aof_buf,并按 appendfsync 策略同步到旧 AOF 文件——保证旧 AOF 文件机制在整个重写期间依然正确,万一重写失败,数据安全不受影响;
- 子进程(3.2):子进程只有fork 之前的内存数据;fork 之后父进程发生的修改,父进程会**再写一份到 AOF 重写缓冲区(aof_rewrite_buf)**中;
④ 子进程写新文件:子进程根据内存快照,把数据转化为最终状态的写命令,合并写入新的 AOF 文件;
⑤ 收尾(信号通知 + 补课 + 替换):
- 5.1 新文件写入完成后,子进程发送信号通知父进程;
- 5.2 父进程把重写缓冲区中保存的命令(即 fork 之后的增量修改)追加到新 AOF 文件——保证新文件也是最新的;
- 5.3新 AOF 文件原子替换旧 AOF 文件,重写完成。
❗一句话理解双缓冲区:aof_buf 保证「旧 AOF 始终可用」(保底),aof_rewrite_buf 记录「fork 之后的增量」(补课)——两者配合,让重写全程无锁、不停服、随时可失败回退。
五、启动时数据恢复
当 Redis 启动时,会根据 RDB 和 AOF 文件的内容进行数据恢复:
- 开启了 AOF→ 优先加载 AOF 文件恢复数据(AOF 更完整);
- 没有开启 AOF→ 尝试加载 RDB 文件;
- 两者都没有 → 空库启动;
- 文件损坏时 Redis 会拒绝启动,可用
redis-check-rdb/redis-check-aof工具检测修复。
六、总结与对比
| 对比项 | RDB | AOF |
|---|---|---|
| 本质 | 内存数据的二进制快照 | 写命令的文本日志 |
| 实时性 | 差(两次快照之间的数据会丢) | 好(everysec 最多丢 1 秒) |
| 文件体积 | 小(紧凑压缩) | 大(有重写机制压缩) |
| 恢复速度 | 快(直接载入二进制) | 慢(逐条重放命令) |
| 主要用途 | 冷备、主从全量复制 | 实时持久化(主流方案) |
| 核心命令 | bgsave | bgrewriteaof |
❗本章重点回顾:
- Redis 提供了 RDB 和 AOF 两种持久化方案:RDB 视为内存的快照,内容紧凑、恢复快,但开销较大、不适合实时持久化,一般用于冷备和主从复制;AOF 保存的是修改命令,恢复时需要重放,并有重写机制定期压缩文件;
- RDB 和 AOF 都使用fork 创建子进程,利用 Linux 父子进程共享内存(写时复制)的特点进行持久化,尽可能不影响主进程继续处理命令;
- AOF 的重写依靠aof_buf + aof_rewrite_buf 双缓冲区配合,做到不停服、数据安全;
- 生产环境的经典选择:AOF(everysec)保实时 + RDB 定期做冷备,两者互补。