前言
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 默认配置:
text
save 900 1
save 300 10
save 60 10000
1.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 文件大小 >
重写流程(以未开启混合模式为例)
下图清晰展示了内存操作与磁盘落盘的区分:
磁盘 操作系统 子进程 主进程 客户端 磁盘 操作系统 子进程 主进程 客户端 触发 bgrewriteaof 根据内存数据生成精悍命令 loop [处理写命令] par [子进程执行重写(不读旧AOF)] [主进程继续服务] fork() 系统调用 子进程创建成功 启动 遍历共享内存(只读) 写入临时新AOF文件(纯命令) 写命令 修改内存(可能触发COW) 将命令追加到旧AOF文件(根据appendfsync) 将命令写入 aof_rewrite_buf 重写完成 将 aof_rewrite_buf 中增量命令追加到临时新AOF文件 原子替换(rename)旧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 开启方式
text
appendonly yes
aof-use-rdb-preamble yes
3.3 工作流程(重点:aof_buf 和 aof_rewrite_buf 同时存在)
磁盘 操作系统 子进程 主进程 客户端 磁盘 操作系统 子进程 主进程 客户端 loop [每个写命令] par [子进程: 写RDB头部] [主进程: 服务写命令] fork() 子进程创建 启动 遍历共享内存(只读) 以RDB格式写入临时新AOF文件(头部) 写命令 修改内存(触发COW) 命令追加到旧AOF文件(aof_buf → 落盘) 命令也写入 aof_rewrite_buf RDB头部完成 将 aof_rewrite_buf 增量命令追加到临时文件(尾部AOF) 原子替换旧AOF文件
关键问题 :重写期间,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:
text
appendonly yes
aof-use-rdb-preamble no
4.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)
磁盘 操作系统 子进程 主进程 客户端 磁盘 操作系统 子进程 主进程 客户端 不再有 aof_rewrite_buf loop [每个写命令] par [子进程: 生成新BASE文件] [主进程: 服务写命令] 旧文件被标记为历史,后续清理 fork() 子进程创建 启动 遍历共享内存(只读) 将内存数据写入新 base.aof(精简命令) 写命令 修改内存(触发COW) 将命令追加到**新的** incr.aof 文件(实时落盘) 新BASE文件完成 原子更新 manifest 文件,指向新 base + 新 incr
核心变化:
- 增量命令不再积攒在内存缓冲区 ,而是直接写入磁盘上的新 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 手动修复工具
bash
redis-check-aof --fix appendonly.aof
工作原理 :逐条解析 AOF 文件,找到第一个无法解析的命令位置,截断该位置之后的所有内容,生成一个结构完整但可能丢失尾部数据的文件。
⚠️ 注意:该工具不能修复文件中间的逻辑错误,也不支持混合持久化文件的头部 RDB 部分(因为 RDB 是二进制格式)。
5.3 RDB 文件校验
bash
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 耗时
bash
redis-cli INFO stats | grep latest_fork_usec
6.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)
text
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!