Redis 持久化完全指南:从 RDB、AOF 到 MP-AOF
前言
Redis 作为内存数据库,持久化是保证数据不丢失的基石。它提供了两种原生持久化方式:RDB(快照)和AOF(追加日志),并在 4.0 版本之后推出了混合持久化,7.0 版本又引入了MP-AOF架构。本文会从原理、配置、优缺点到底层实现细节(比如写时复制到底是谁触发的)一步步讲清楚,让你彻底搞懂 Redis 的数据安全机制。
一、RDB:内存快照
1.1 是什么?
RDB 会把当前 Redis 内存中的数据生成一份二进制快照,保存到.rdb文件中(默认dump.rdb)。可以通过redis.conf配置路径和文件名,也可以在运行时用CONFIG SET动态调整。
- 文件特点:每次生成的新 RDB 文件会覆盖旧文件。
- 恢复速度:因为是直接加载二进制数据,所以恢复非常快。
1.2 触发方式
| 方式 | 命令/配置 | 特点 |
|---|---|---|
| 手动同步 | SAVE | 主进程执行,阻塞所有客户端,仅用于维护或调试 |
| 手动异步 | BGSAVE | fork 子进程执行,主进程继续服务。推荐手动触发时使用 |
| 自动触发 | save <秒> <修改次数> | 例如save 900 1:15分钟内至少1次修改就后台执行BGSAVE |
Redis 默认配置:
save 900 1 save 300 10 save 60 100001.3 底层原理:写时复制(Copy-On-Write)
执行BGSAVE时,主进程会fork()一个子进程。fork之后:
- 父子进程的虚拟地址指向相同的物理内存,页表项被标记为只读。
- 子进程负责遍历内存、生成 RDB 文件(只读操作,不会触发 COW)。
- 父进程收到写命令(如
SET)需要修改某内存页时,CPU 检测到只读标志 → 触发缺页异常 → 内核为父进程分配新物理页,并复制原页内容 → 父进程在新页上写入。 - 结果:子进程仍持有原物理页(快照一致性),父进程使用新页(写入新数据)。
❗️ 常见误解:是父进程的写操作触发 COW,而不是子进程。子进程在 RDB 期间从不修改内存。
1.4 优点与缺点
| 优点 | 缺点 |
|---|---|
| 恢复速度快(直接加载二进制) | 数据丢失风险高:两次快照之间的数据会丢失 |
| 文件紧凑,适合备份与异地容灾 | fork()开销大:大内存时页表复制可能阻塞主进程数百毫秒 |
| 对写性能影响较小 | COW 期间大量写入会导致内存翻倍(每个修改页都要复制) |
1.5 适用场景
- 可以容忍几分钟数据丢失(如缓存、日志分析)
- 对恢复速度要求很高
- 不希望有太多磁盘 I/O 和文件体积
二、AOF:追加日志
2.1 是什么?
AOF(Append Only File)以日志形式记录每个写命令(实际存储的是 Redis 序列化协议 RESP 格式)。当 Redis 重启时,会重放 AOF 文件中的所有命令来重建数据。
- 文件名默认
appendonly.aof。 - 追加写,不会覆盖旧内容,因此文件会越来越大。
2.2 写入与落盘流程
- 主线程执行写命令 → 修改内存数据。
- 将命令追加到内存中的 AOF 缓冲区(
aof_buf)。 - 根据
appendfsync策略,由后台线程(或主线程,取决于策略)将缓冲区数据调用fsync写入磁盘。
appendfsync三种策略:
| 策略 | 行为 | 性能 | 数据丢失风险 |
|---|---|---|---|
always | 每个写命令后都fsync | 最差 | 最多丢失一个命令 |
everysec(默认) | 每秒fsync一次 | 平衡 | 最多丢失 1 秒数据 |
no | 由操作系统决定何时fsync | 最好 | 可能丢失大量数据 |
💡 注意:
everysec策略下,fsync由后台线程执行,不阻塞主线程。
2.3 AOF 重写(Rewrite)
AOF 文件会记录重复或无效命令(例如对同一个 key 反复SET),导致文件膨胀。重写机制会生成一个最小命令集的新 AOF 文件来替换旧文件。
关键认知:AOF 重写不是基于旧的 AOF 文件进行修改或压缩,而是子进程直接遍历当前内存中的数据,生成能重建当前数据状态的最精简命令,写入一个临时文件。最后主进程把重写期间的增量命令补上,再原子替换。
- 手动触发:
BGREWRITEAOF - 自动触发条件(需同时满足):
- 当前 AOF 文件大小 >
auto-aof-rewrite-min-size(默认 64 MB) - 当前文件比上次重写后的大小增长了 100%(由
auto-aof-rewrite-percentage控制)
- 当前 AOF 文件大小 >
重写流程(以未开启混合模式为例)
下图清晰展示了内存操作与磁盘落盘的区分:
核心点:
aof_buf:常规缓冲区,负责将命令刷到旧 AOF 文件(重写期间旧文件依然在正常追加)。aof_rewrite_buf:重写专用缓冲区,记录重写期间的所有增量命令,待子进程完成后追加到新 AOF 文件尾部。
2.4 优点与缺点
| 优点 | 缺点 |
|---|---|
数据更安全:everysec最多丢一秒数据 | AOF 文件通常比 RDB 大 |
| 支持时间点恢复(配合外部工具) | 恢复速度慢(需重放所有命令) |
| AOF 文件是纯文本,可读、可审计 | 持续写入磁盘,对 I/O 压力较大 |
重写时fork同样有 COW 内存开销,且aof_rewrite_buf可能积压导致 OOM |
2.5 适用场景
- 对数据完整性要求高(支付、交易、用户资产)
- 可以接受较长的重启恢复时间
- 希望有操作日志用于审计
三、混合持久化(Redis 4.0+,7.0 前的主流模式)
3.1 是什么?
混合持久化是AOF 重写的一种增强模式。当执行BGREWRITEAOF时,生成的 AOF 文件不再是纯文本命令,而是:
[RDB 二进制快照] + [后续的增量 AOF 命令]这样既保留了 RDB 快速恢复的优点,又结合了 AOF 数据丢失少的特性。
3.2 开启方式
appendonly yes aof-use-rdb-preamble yes3.3 工作流程(重点:aof_buf 和 aof_rewrite_buf 同时存在)
关键问题:重写期间,aof_buf和aof_rewrite_buf保存相同的命令文本。高写入场景下,两个缓冲区同时膨胀,内存占用翻倍,极易引发OOM。
3.4 恢复流程
Redis 启动加载 AOF 文件时,检查文件开头是否有REDIS魔数:
- 有 → 混合文件:先快速加载 RDB 部分,再重放尾部 AOF 命令。
- 无 → 纯 AOF 文件:重放所有命令。
四、MP-AOF(Redis 7.0+,需主动开启)
4.1 设计目标
彻底解决混合持久化中aof_rewrite_buf导致的内存翻倍问题,消除 OOM 风险。
4.2 如何开启
注意:MP-AOF 与旧版混合持久化互斥。要启用 MP-AOF,必须关闭aof-use-rdb-preamble:
appendonly yes aof-use-rdb-preamble no4.3 文件结构
不再是单一的appendonly.aof,而是一组文件:
| 文件类型 | 命名示例 | 内容 |
|---|---|---|
| BASE 文件 | appendonly.aof.1.base.rdb或.aof | 全量快照(开启混合模式时为 RDB,否则为 AOF 命令) |
| INCR 文件 | appendonly.aof.1.incr.aof | 增量 AOF 命令 |
| MANIFEST 文件 | appendonly.aof.manifest | 清单文件,记录当前激活的 BASE 和 INCR 文件组合 |
由于
aof-use-rdb-preamble no,BASE 文件此时是纯 AOF 格式(.base.aof)。但你可以选择开启混合模式(yes)让 BASE 变成 RDB,不过那会退回到旧版单文件模式。所以 MP-AOF 下 BASE 实际是 AOF 格式。
4.4 重写流程(去掉了 aof_rewrite_buf)
核心变化:
- 增量命令不再积攒在内存缓冲区,而是直接写入磁盘上的新 INCR 文件。
- 完全移除了
aof_rewrite_buf,杜绝了 OOM 风险。 aof_buf仍然存在,用于将命令刷入当前活跃的 INCR 文件。
4.5 恢复流程
Redis 启动时,读取manifest文件,获取当前有效的 BASE 文件和 INCR 文件列表,按顺序加载:
- 加载 BASE 文件(全量数据)。
- 依次重放所有 INCR 文件中的命令。
五、持久化文件损坏与修复
5.1 AOF 文件尾部截断
常见于机器突然掉电,导致 AOF 文件末尾命令不完整。可通过配置aof-load-truncated控制行为:
yes(默认):忽略末尾不完整命令,加载有效部分,并打印警告日志。no:拒绝启动,需手动修复。
5.2 手动修复工具
redis-check-aof--fixappendonly.aof工作原理:逐条解析 AOF 文件,找到第一个无法解析的命令位置,截断该位置之后的所有内容,生成一个结构完整但可能丢失尾部数据的文件。
⚠️ 注意:该工具不能修复文件中间的逻辑错误,也不支持混合持久化文件的头部 RDB 部分(因为 RDB 是二进制格式)。
5.3 RDB 文件校验
redis-check-rdb dump.rdb用于检查 RDB 文件完整性,可输出详细报告。
六、fork 开销与写时复制(COW)深度解析
6.1 fork 为什么昂贵?
fork()不需要复制物理内存,但需要复制父进程的页表。对于 10GB 的 Redis 进程,页表大小约 20MB,复制耗时与内存大小成正比(约每 GB 20ms)。在虚拟化环境(特别是 Xen)中,fork()耗时会更长。
6.2 COW 谁触发?
- 子进程(RDB 快照或 AOF 重写)只读共享内存,不触发 COW。
- 主进程收到写命令,修改只读页时触发缺页中断 → MMU 分配新物理页 → 复制数据 → 主进程在新页上写入。
6.3 透明大页(THP)的影响
THP 使用 2MB 大页代替 4KB 小页,fork()时页表变小,fork()更快。但 COW 时,每次复制 2MB 数据,导致内存开销剧增。生产环境建议关闭 THP。
6.4 监控 fork 耗时
redis-cli INFO stats|greplatest_fork_usec6.5 优化建议
- 控制单个 Redis 实例内存 ≤ 10GB。
- 尽量部署在物理机而非虚拟机。
- 适当调低 AOF 重写触发频率。
- 关闭 THP。
- 从节点执行备份,转移
fork压力。
七、生产环境最佳实践
| 场景 | 推荐方案 |
|---|---|
| 缓存服务,可容忍分钟级数据丢失 | 仅 RDB(save 900 1等) |
| 核心数据,要求最多丢一秒,且 Redis 版本 ≥ 7.0 | MP-AOF(appendonly yes+aof-use-rdb-preamble no+appendfsync everysec) |
| 核心数据,Redis 版本 < 7.0 | 混合持久化(aof-use-rdb-preamble yes),需监控内存防止 OOM |
| 极端性能要求,可接受大量数据丢失 | 关闭持久化(仅主从复制) |
配置示例(Redis 7.0 MP-AOF)
appendonly yes appendfsync everysec aof-use-rdb-preamble no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb结语
从 RDB 的快照机制,到 AOF 的命令日志,再到混合持久化与 MP-AOF 的演进,Redis 持久化体系始终在数据安全、恢复速度与性能开销之间寻求平衡。理解这些机制不仅能帮你做出正确的配置选择,还能在遇到性能瓶颈或数据丢失时快速定位原因。
核心回顾:
fork+ COW 是所有持久化操作的基础,开销不可避免。- AOF 重写的本质是基于内存重建,而非修改旧文件。
- 混合持久化用
aof_rewrite_buf换来了快速恢复,但牺牲了内存稳定性。 - MP-AOF 用多文件 + 实时落盘替代了缓冲区,根治了 OOM 问题。
希望这篇博客能帮你彻底掌握 Redis 持久化。Happy Coding!
