核心目标:理解 RDB、AOF、混合持久化三种方式的机制、代价与恢复语义;掌握 fork + COW、fsync 策略、AOF 重写等关键概念;能设计符合业务丢失容忍度的持久化策略,并在文件损坏后完成修复与恢复。
前置知识:完成 Part 1(单线程事件循环——持久化是"阻塞点"的重要来源)与 Part 3(内存结构)。
验证环境:Redis 8.10.0(cygwin 移植版,主实例 6379 用于常规实验,隔离实例 6380 用于断电/损坏等破坏性实验)、redis-py 8.1.0、Python 3.11.6、Windows 11。最后复核日期:2026-08-07。
0. 本篇问题场景:数据"没丢"只是运气
"Redis 是内存数据库,重启就清空"是常见的误读。真实情况是:Redis默认配置下(appendonly no)只做 RDB 快照,且快照触发频率可能很低——kill -9或断电后,上一次快照之后写入的所有数据都会消失。
先定义本系列反复用到的词:丢失窗口(数据丢失时间窗)——从最后一次"落盘"到故障发生之间的时间。业务能容忍丢失多少秒,直接决定持久化配置:
| 业务 | 丢失容忍 | 合理的持久化策略 |
|---|---|---|
| 商品缓存(可重建) | 分钟级 | RDB 即可,甚至不持久化 |
| 购物车/会话 | 秒级 | AOF everysec |
| 支付流水/状态机 | 零容忍 | AOF always + 主从 + 备份 |
本篇用隔离实例把三种持久化的"保存-恢复-损坏-修复"完整演一遍。
1. RDB:内存快照
1.1 机制:fork + Copy-On-Write
RDB 是某时刻全内存数据的二进制快照。触发SAVE(同步)或BGSAVE(后台)时:
- 主进程
fork()一个子进程; - 子进程把内存中的数据序列化写入
dump.rdb; - 主进程继续服务客户端。
关键在fork + COW(写时复制):
COW 的代价:fork 瞬间复制页表(大实例可能几十~几百毫秒);写命令触发页复制(写入越频繁,复制越多);快照期间内存可能短时上升(被复制的页)。
1.2 触发方式
| 方式 | 命令/配置 | 阻塞性 |
|---|---|---|
| 手动同步 | SAVE | 阻塞主进程直到写完 |
| 手动后台 | BGSAVE | fork 瞬间阻塞,之后后台写 |
| 自动 | save 900 1/save 300 10/save 60 10000 | 同 BGSAVE |
| 关闭自动 | save "" | 手动控制 |
1.3 实测:保存、校验、恢复
在隔离实例上完整走一遍(写入 100 个键 → SAVE → 校验 → 重启):
$ redis-cli -p 6380 SAVE OK $ ls -la dump.rdb -rw-r--r-- 1875 dump.rdb ← 二进制快照,1875 字节 $ redis-check-rdb dump.rdb [offset 1875] Checksum OK [offset 1875] \o/ RDB looks OK! \o/ [info] 100 keys read (重启实例) $ redis-cli -p 6380 DBSIZE 100 ← 数据完整恢复 $ redis-cli -p 6380 GET order:42>* Loading RDB produced by version 8.10.0 * RDB age 15 seconds * Done loading RDB, keys loaded: 100, keys expired: 0.RDB 的定位是"定期快照":恢复快(直接载入二进制)、文件紧凑,但丢失窗口 = 距上次快照的时间。
2. AOF:追加式命令日志
2.1 机制:把"写命令"本身记下来
AOF(Append Only File)记录每条修改数据的命令(RESP 协议格式),重启时重放命令重建数据。相比 RDB,它把丢失窗口压缩到"最后一次 fsync 到故障"之间。
写入链路:
客户端写命令 → 命令执行(内存生效) → 写入 AOF 缓冲 → 按 appendfsync 策略刷入 OS 文件缓存 → 按策略 fsync 落盘appendfsync三个档位的丢失窗口:
| 策略 | fsync 时机 | 丢失窗口 | 性能 |
|---|---|---|---|
always | 每条命令后 | 0(严格说:命令返回前已落盘) | 最慢,每秒数千~数万次 fsync |
everysec | 每秒一次 | 最多 1 秒 | 生产默认,性能/安全平衡 |
no | 交给 OS 决定 | 取决于 OS 刷盘时机(理论最长可达秒级~分钟级) | 最快 |
2.2 7.0+ 的多部件 AOF:文件结构变了
Redis 7.0 重构了 AOF:不再是单个appendonly.aof,而是一个appendonlydir/目录,由三部分组成:
appendonlydir/ ├── appendonly.aof.manifest ← 清单:base/incr 文件与顺序 ├── appendonly.aof.1.base.rdb ← base 文件(当前全量;纯 AOF 或混合 RDB 头) └── appendonly.aof.1.incr.aof ← incr 文件(base 之后的增量命令)实测的 manifest 内容:
file appendonly.aof.1.base.rdb seq 1 type b file appendonly.aof.1.incr.aof seq 1 type i startoffset 0incr 文件就是 RESP 协议的命令日志(可读):
*1 $5 MULTI *2 $6 SELECT $1 0 *3 $3 ...AOF 重写(BGREWRITEAOF)把当前全量数据写成新的 base 文件,并清空/截断增量——防止 AOF 无限膨胀。重写同样是 fork 子进程 + 后台进行,与 RDB 的 COW 机制一致。
2.3 实测:AOF 保存与恢复
$ redis-cli -p 6380 BGREWRITEAOF Background append only file rewriting started $ ls appendonlydir/ appendonly.aof.2.base.rdb 21875 字节 ← 重写后的全量 appendonly.aof.2.incr.aof 0 字节 appendonly.aof.manifest $ redis-check-aof appendonlydir/appendonly.aof.manifest BASE AOF appendonly.aof.2.base.rdb is valid All AOF files and manifest are valid (重启实例) $ redis-cli -p 6380 DBSIZE 1000 ← 完整恢复 $ redis-cli -p 6380 GET order:99>3. 混合持久化:RDB 头 + AOF 尾纯 AOF 在启动加载时要重放全部命令,数据量大时启动慢。混合持久化(aof-use-rdb-preamble yes,7.x 默认开启)让 AOF 重写后的 base 文件直接用 RDB 格式(快照加载),之后的增量继续用 AOF 追加:
$ head -c 9 appendonlydir/appendonly.aof.2.base.rdb | xxd 00000000: 5245 4449 5330 3031 35 REDIS0015 ← RDB 魔数
redis-check-aof对它的识别:
RDB preamble is OK, proceeding with AOF tail...
加载优先级:只要 AOF 开启,启动时只从 AOF(含混合 base)加载,不读dump.rdb——AOF 永远包含更新的数据。RDB 只在 AOF 关闭时生效。
4. 失败实验:损坏与修复
4.1 AOF 损坏:启动失败 → 修复 → 恢复
向 incr AOF 文件中间插入垃圾字节后启动:
# Bad file format reading the append only file appendonlydir/appendonly.aof.2.incr.aof at offset 0. # make a backup of your AOF file, then use ./redis-check-aof --fix <filename.manifest>. # Alternatively you can set the 'aof-load-corrupt-tail-max-size' configuration option # to 28 and restart the server.
注意细节:base 文件加载成功了(Done loading RDB, keys loaded: 1000),失败在损坏的 incr 段——多部件 AOF 让"全量部分无损,增量部分可修"成为可能。修复:
$ redis-check-aof --fix appendonlydir/appendonly.aof.manifest RDB preamble is OK, proceeding with AOF tail... ... AOF appendonly.aof.2.incr.aof format error AOF analyzed: ... ok_up_to=0, diff=28 This will shrink the AOF ... from 28 bytes, with 28 bytes, to 0 bytes Continue? [y/N]: y Successfully truncated AOF appendonly.aof.2.incr.aof All AOF files and manifest are valid
重启后数据完整恢复(1000 键,来自未损坏的 base):
$ redis-cli -p 6380 DBSIZE 1000 $ redis-cli -p 6380 GET product:999>4.2 RDB 损坏:CRC 校验拦截破坏dump.rdb的 checksum 后:
$ redis-check-rdb dump.rdb --- RDB ERROR DETECTED --- [offset 7375] RDB CRC error [additional info] While doing: check-sum [info] 500 keys read
启动时同样被拦截:
# Wrong RDB checksum expected: (5baeea71929c254a) but got (a451158e6d63dab5). Aborting now. # Internal error in RDB reading offset 0, function at rdb.c:5062 -> RDB CRC error
Redis 拒绝加载损坏的 RDB 并直接退出(默认rdbchecksum yes且不做自动修复)——这比"带病启动"安全:宁可起不来,也不给应用喂一份被篡改的数据。生产修复路径是:用备份 RDB → 或用redis-check-rdb报告损坏程度 → 必要时结合 AOF 恢复。
4.3 "断电"实验:appendfsync no 的意外结果
用taskkill /F强制杀掉 AOF(appendfsync no)实例模拟断电,重启后1000 个键全部还在:
$ taskkill /F /PID 46440 # 模拟断电 成功: 已终止 PID 为 46440 的进程。 (重启) $ redis-cli -p 6380 DBSIZE 1000
这个结果必须谨慎解读:appendfsync no只是"不主动 fsync",数据仍会经OS 文件缓存异步刷盘——Windows 上缓存刷盘非常快,所以"写入后立即 kill"通常已经落盘。它不能证明 no 策略安全:如果 OS 自身崩溃或整机断电,尚未刷盘的缓存照样丢;丢失窗口在理论上依然存在且不可控。这个实验的意义是说明:“实测没丢"≠"策略安全”,丢失窗口是概率与环境的函数。生产仍应选everysec(丢失窗口有界 ≤ 1s)或always(零丢失)。
5. 持久化策略设计
5.1 三种方式对比
维度 RDB AOF(everysec) 混合(默认) 丢失窗口 距上次快照(分钟级) ≤ 1 秒 ≤ 1 秒 恢复速度 快(二进制载入) 慢(重放命令) 快(RDB 头 + 增量) 文件大小 紧凑 可能膨胀(需重写) 紧凑(重写后 RDB 头) 写路径开销 fork + COW 追加 + 每秒 fsync 同 AOF 故障文件修复 redis-check-rdb(只报告)redis-check-aof --fix(可截断修复)同 AOF
5.2 生产推荐组合
appendonly yes # 打开 AOF appendfsync everysec # 丢失窗口 ≤ 1s(默认) aof-use-rdb-preamble yes # 混合持久化(默认) save 900 1 300 10 60 10000 # 定期 RDB 作为额外备份点
再加三道保险(后续 Part 10/12 展开):
- 主从复制:至少一个从库,主库故障自动切换;
- 异地备份:定期把 RDB/快照拷贝到其他存储;
- 恢复演练:周期性地"备份 → 恢复到干净实例 → 校验数据"。
6. 版本与环境差异
差异点 7.0 之前 7.x / 本机 8.10 影响 AOF 文件形态 单个appendonly.aof 多部件目录appendonlydir/(manifest + base + incr) 备份/修复命令参数变化;redis-check-aof --fix传 manifest 文件 aof-use-rdb-preamble4.0 引入,5.0 起默认开启 默认 yes(混合持久化) base 文件是.rdb格式 AOF 加载失败策略 直接拒绝 仍拒绝,但可配aof-load-corrupt-tail-max-size容忍尾部损坏 高可用场景可有限容忍 rdbchecksum默认 yes 默认 yes(CONFIG GET实测) 损坏 RDB 启动即 abort(§4.2)
注意:损坏与断电实验均在隔离实例 6380 上进行,未触碰生产主实例 6379——这类实验永远不要在生产/开发共享实例上做。
7. 测试与验收
- 建议测试:备份/恢复往返(SAVE → 重启 → 校验键数)、
redis-check-*命令可用性、损坏文件修复脚本(隔离实例); - 生产验收清单见下。
本篇验收清单:
- 能画出 fork + COW 的时序并说出 RDB 的三个阻塞点(fork、页复制、写盘);
- 能说出
appendfsync三档的丢失窗口(0 / ≤1s / 取决于 OS); - 知道 7.0+ AOF 是多部件目录结构,base/incr/manifest 各自职责;
- 知道混合持久化的加载流程(RDB 头 + AOF 尾)与"AOF 优先于 RDB"的规则;
- 能完成一次 AOF 损坏修复(备份 →
--fix→ 重启验证); - 能为"秒级容忍/零容忍/可重建"三类业务各设计一套持久化配置。
8. 常见误区
- “Redis 默认持久化,重启不丢数据”——默认只 RDB 快照且触发频率低,丢失窗口分钟级起步(§0)。
- “
SAVE和BGSAVE一样安全”——SAVE同步阻塞主进程;生产手动触发永远用BGSAVE。 - “AOF 是日志文件,改一行加一行”——7.0+ 是多部件结构 + 重写机制;直接编辑文件是灾难(§2.2)。
- “RDB 损坏了
redis-check-rdb --fix能修”——redis-check-rdb只报告不修复;修复靠备份或 AOF(§4.2)。 - “实测 kill -9 没丢数据,所以 appendfsync no 安全”——丢失窗口是概率性的,OS 崩溃/断电才是 no 的真正风险(§4.3)。
- “开启 AOF 后 RDB 就没用了”——RDB 仍是恢复快、文件小的备份点;混合持久化的 base 就是 RDB 格式(§5)。
9. 本篇小结
回到开篇:持久化的本质是用可控的写路径开销,换取有界的丢失窗口:
- RDB:全量快照,恢复最快,丢失窗口 = 距上次快照;
- AOF:命令追加 + fsync 策略,丢失窗口有界(everysec ≤ 1s);
- 混合:base 用 RDB(快加载)+ incr 用 AOF(低丢失),7.x 默认形态;
- 损坏防御:RDB 靠 CRC 校验拒绝启动,AOF 靠
redis-check-aof --fix截断修复——备份永远是最可靠的恢复手段。
下一篇 Part 6:过期、内存淘汰与内存优化 把"内存"管起来:TTL 的惰性/定期删除、maxmemory八种淘汰策略、大 key 识别与内存治理,回答"设置了过期时间为什么内存还在涨"。
10. 官方资料
- Redis persistence 官方文档:https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/
- RDB 文件格式说明:https://rdb.readthedocs.io/
redis-check-aof/redis-check-rdb:https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/BGREWRITEAOF/BGSAVE:https://redis.io/docs/latest/commands/