Redis 持久化:RDB 与 AOF
Redis 是内存数据库,进程退出后数据就没了。持久化要解决两个问题:重启后能恢复多少数据,以及持久化过程会不会拖慢主线程。本文以 Redis 7.x 为基准,拆解 RDB 的 fork 与写时复制、AOF 的刷盘策略、重写流程,以及 7.0 引入的多部分 AOF 带来的变化。
一、RDB:某一时刻的全量快照
RDB 是把整个数据集序列化成二进制文件,文件以 REDIS0011 魔数开头,依次写入辅助字段、各个数据库的键值对、EOF 和 CRC64 校验和。恢复时直接解析文件重建内存,速度比逐条重放命令快得多。
触发方式有三类:
- 配置
save <seconds> <changes>,例如默认的save 3600 1 300 100 60 10000; - 手动执行
SAVE(阻塞主线程,禁用)或BGSAVE; - 主从全量同步、
SHUTDOWN时由服务端自动触发。
BGSAVE 会 fork 一个子进程,子进程持有父进程内存的只读视图并写盘,父进程继续处理命令。这里依赖操作系统的写时复制(Copy-On-Write):fork 只复制页表,不复制物理页;父进程每修改一页,内核才为该页复制一份。因此快照期间的内存增量约等于被修改的数据页大小,写越频繁,COW 放大越明显。
fork 本身不是免费的。它要复制页表,1 GB 内存大约耗时 10~20 ms,期间主线程被阻塞。内存越大、页表项越多,停顿越久。
# 观察 fork 与快照状态
127.0.0.1:6379> INFO persistence
rdb_bgsave_in_progress:0
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:0
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:0
rdb_last_cow_size 是上一次快照期间 COW 复制的字节数,持续偏高说明写入压力大,需要评估快照频率。
二、RDB 的关键配置
save 3600 1 300 100 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
rdb-del-sync-files no
stop-writes-on-bgsave-error yes:快照失败后拒绝写入,避免"以为有持久化、其实没有"。用了监控告警的团队可以关掉,但要确保告警真的有人看。rdbcompression yes:对字符串做 LZF 压缩,省磁盘和网络,代价是少量 CPU。rdbchecksum yes:写入 CRC64,加载时校验,能发现文件损坏。rdb-del-sync-files no:全量同步后是否删除用于同步的 RDB 文件,默认保留。
RDB 的缺点是会丢数据:两次快照之间的写入全部丢失。它适合做冷备、主从全量同步的载体,不适合作为唯一的持久化手段。
三、AOF:把写命令追加到日志
开启 AOF 后,每条修改数据的命令都会以 RESP 协议追加到文件末尾。恢复时从头重放,得到与内存一致的状态。
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
appendfsync 决定数据安全性与吞吐的平衡:
| 取值 | 行为 | 最多丢多少 | 代价 |
| --- | --- | --- | --- |
| always | 每条命令都 fsync | 最多 1 条 | 受磁盘 IOPS 限制,吞吐骤降 |
| everysec | 后台线程每秒 fsync 一次 | 约 1 秒 | 磁盘慢时主线程可能被阻塞约 2 秒 |
| no | 交给操作系统刷盘 | 约 30 秒 | 机器宕机丢数据多 |
生产环境通常选 everysec。如果磁盘 fsync 延迟高,Redis 检测到上一次 fsync 未完成时会推迟本次 fsync,主线程在写 AOF 缓冲区时可能被阻塞,表现为周期性毛刺。
四、AOF 重写:把日志压小
AOF 会不断膨胀:对同一个 key 执行 100 次 INCR,日志里就有 100 条命令,而恢复只需要一条 SET。BGREWRITEAOF 就是根据当前内存状态生成一份最小命令集。
重写同样靠 fork 子进程完成:
- 父进程 fork 出子进程,子进程遍历内存写新 AOF;
- 父进程把期间的新写命令同时写入
aof_buf和重写缓冲区; - 子进程完成后,父进程把重写缓冲区的内容追加到新文件末尾,再原子替换旧文件。
第 2 步的重写缓冲区是内存开销的主要来源。如果重写期间写入量很大,缓冲区可能撑爆内存,这也是 7.0 改造多部分 AOF 的动机之一。
五、Redis 7.0 的多部分 AOF
7.0 之前,AOF 是单个文件,重写时要同时维护旧文件、新文件和缓冲区,还要处理替换瞬间的写入。7.0 把 AOF 拆成一个目录:
appendonlydir/
├── appendonly.aof.manifest
├── appendonly.aof.1.base.rdb
├── appendonly.aof.1.incr.aof
└── appendonly.aof.2.incr.aof
manifest记录文件清单与顺序;base是基准文件,aof-use-rdb-preamble yes时采用 RDB 格式,即混合持久化;incr是增量命令文件,重写时直接开一个新的 incr 文件,旧文件在合并完成后删除。
好处是重写期间不需要把增量再拷回 base,减少一次内存缓冲和磁盘拷贝;manifest 也让损坏修复的粒度更细。升级到 7.x 后,旧的单文件 AOF 会在启动时自动转换为多部分格式。
六、混合持久化与恢复顺序
混合持久化指 base 用 RDB、增量用 AOF:加载时先快速恢复 RDB 快照,再重放少量增量命令,兼顾速度与数据安全。7.0 起 aof-use-rdb-preamble 默认为 yes,新写入的 base 文件就是 RDB 格式。
启动时的加载顺序是:如果 AOF 开启,优先加载 AOF;否则加载 RDB。因为 AOF 通常更完整,二者同时存在时以 AOF 为准。
# 检查 AOF 文件是否完整
redis-check-aof --fix appendonlydir/appendonly.aof.1.incr.aof
# 查看当前持久化状态
redis-cli INFO persistence | grep -E "aof_enabled|aof_last_write_status|aof_rewrite_in_progress"
aof-load-truncated yes 允许加载被截断的 AOF 尾部并打印警告,适合掉电场景;如果要求严格,设为 no,让启动直接失败,由人工确认。
七、怎么选
- 只做缓存、能接受重启后回源:可以关闭持久化,或只保留低频 RDB 做冷备。
- 存业务数据、不能接受明显丢失:AOF
everysec+ RDB 定期快照,即混合持久化。 - 追求极致安全:AOF
always,但必须确认磁盘 IOPS 能扛住,否则延迟不可接受。
常见坑 / 最佳实践
- 务必设置
vm.overcommit_memory=1。 fork 依赖内存超额分配,设为 0 时大内存实例的BGSAVE可能直接失败。 - 关闭透明大页(THP)。 THP 会把 COW 的复制单位从 4 KB 放大到 2 MB,快照期间延迟明显升高,应在系统层设置
never。 - 不要在主库做全量持久化压测。
BGSAVE的 fork 停顿和 COW 放大都会影响在线请求,压测请在从库或独立实例进行。 - 监控
rdb_last_bgsave_status和aof_last_write_status。 磁盘满或权限错误会让持久化静默失败,只看进程存活发现不了。 - 重写频率要合理。
auto-aof-rewrite-percentage 100表示文件翻倍才重写,过于频繁的重写会反复 fork,放大延迟抖动。
小结
- RDB 是某一时刻的全量快照,恢复快但会丢两次快照之间的数据;
BGSAVE依赖 fork 与写时复制,内存越大停顿越久。 - AOF 记录写命令,
everysec是安全与性能的平衡点;文件会膨胀,需要重写。 - Redis 7.0 用
base + incr + manifest的多部分结构替代单文件 AOF,降低重写期间的内存与拷贝开销。 - 混合持久化(RDB base + AOF 增量)是业务数据的推荐配置,恢复时优先加载 AOF。
- 持久化的稳定性取决于系统参数:
overcommit_memory=1、关闭 THP、磁盘余量与告警缺一不可。
- 点赞
- 收藏
- 关注作者
评论(0)