
引言
Redis 是典型的内存型键值数据库。它的高性能、高吞吐、低延迟,离不开把数据主要放在内存中的设计。但纯内存也意味着:"断电 / 进程崩溃 / 机器重启"可能造成全部数据丢失。
为了兼顾"内存速度"与"数据持久性",Redis 提供了持久化机制,将内存中的数据写到磁盘,以便重启后恢复。Redis 目前主要支持两大类持久化方式:
- RDB --- 快照方式
- AOF --- 日志方式
在实际部署中,也常见将两者组合使用,以兼顾恢复速度与数据安全性。
本文将从原理、实现流程、优缺点、演化(比如 Redis 7.0 引入的混合方式)等角度,深入剖析 RDB 与 AOF。
一、RDB:快照式持久化
1.1 原理
- RDB 是 Redis 的"整体快照"持久化方式。它会在某些触发条件下(定期或手动触发)将当前内存中的整个数据集(键、值、数据结构等)做一次"拍照",序列化并写到一个二进制文件(默认叫
dump.rdb)。 - 启动时,Redis 会读取该 RDB 文件,将其中的内容反序列化到内存,从而恢复数据状态。
- 为了避免持久化操作阻塞主线程(影响响应),Redis 通过
fork()创建子进程来执行序列化和写文件(临时文件),父进程继续响应客户端;子进程完成后再将临时文件重命名 / 覆盖正式文件,实现原子性替换。 - 序列化格式遵循 Redis 的内部 RDB 格式(包括类型标识、压缩、校验等)以节省空间和加快恢复过程。
Redis 官方文档中对此有明确说明。
1.2 实现流程(典型流程)
下面是简化后的 RDB 快照流程:
-
触发快照
- 配置文件
redis.conf中有save条目(例如save 900 1,意思是 900 秒内至少有 1 次写操作就做快照;也可有多个触发条件) - 手动执行
BGSAVE命令 - 注意:有一个
SAVE命令也是可以用来做快照,但SAVE是同步操作,会阻塞服务器主进程,一般不推荐在生产环境中使用。
- 配置文件
-
fork 子进程
- 父进程继续处理客户端请求
- 子进程在其自己的地址空间里遍历当前内存数据,并做序列化、压缩、写磁盘等工作
- 操作系统利用 Copy-On-Write(写时复制)机制确保父子进程对同一内存页共享,直到某一方写入时才真正复制页面
-
子进程序列化写临时文件
- 将内存中的数据以 RDB 格式序列化写入临时文件(如
temp-db.rdb) - 完成后做 fsync(确保写入磁盘)
- 将内存中的数据以 RDB 格式序列化写入临时文件(如
-
原子替换
- 子进程成功写出、校验无误后,将临时文件重命名 / 移动 / 覆盖为最终的
dump.rdb文件 - 子进程退出
- 子进程成功写出、校验无误后,将临时文件重命名 / 移动 / 覆盖为最终的
-
父进程监控 / 完成
- 父进程得知快照完成(通过信号或状态)
- 后续根据下次触发条件再重复上述流程
-
重启恢复
- 启动 Redis 时,如果检测到已有的
dump.rdb文件(路径、名称符合配置),则读取它,反序列化,将数据加载入内存 - 加载过程中 Redis 会构建内存结构(hash, list, set, zset 等),并完成校验
- 启动 Redis 时,如果检测到已有的
在这整个过程中,父进程在 fork 操作(创建子进程)那一刻会经历短暂暂停(系统调用开销、页表复制、内核态切换等),在大数据量时可能造成较为明显的延迟或卡顿。
1.3 问题:如果在RDB快照式持久化过程执行 bgsave 过程中,redis要写数据,具体是如何做到?
- Redis 在
BGSAVE时 fork 出子进程,子进程负责遍历内存、序列化写入磁盘;父进程继续响应客户端请求,包括写操作。 - 通过操作系统提供的 Copy-On-Write (COW) 机制,当父进程尝试写某个内存页时,操作系统会将该页拷贝(给父进程独立页)后再写,这样子进程仍能看到快照时那一刻的旧页内容。
- 因此写操作与快照是并行的:写操作不会破坏子进程的读取视图。子进程快照看到的是触发
fork那一刻的"静态视图",父进程写入的是后来变化的内容,隔离开来。
那么父进程修改的数据岂不是不会写入快照了?是的
那么修改的数据如何处理?等待下一次快照或者其他方案
1.4 优点
- 恢复速度快:因为重启时只需加载一个紧凑、压缩后的二进制文件,而不用逐条执行命令,尤其对大数据量更有优势。
- 存储文件体积小:RDB 文件是压缩/序列化后的整体快照,通常比 AOF 日志小很多。
- 对运行时性能影响较小:除去 fork 时短暂开销外,正常运行时父进程几乎不用参与磁盘 I/O。
- 适合用于备份 / 冷备份 / 远程复制:RDB 的快照文件作为离线备份、灾备恢复比较方便(整体文件便于传输、归档)。
- 在副本(Replica / Slave)场景中优势:子节点做部分同步(partial sync)时,可以先发 RDB 快照,再同步增量。Redis 在复制机制中对 RDB 支持良好。
1.5 缺点 / 风险
- 数据丢失窗口:因为快照是以时间/写次数触发的,如果 Redis 在两次快照之间发生崩溃或断电,则最近一段写入会丢失。
- fork 开销 / 卡顿:对于大内存应用,fork 和写大快照可能引起明显延迟、内存页写时复制压力、系统抖动。
- 快照过程资源开销:子进程需要遍历全量数据、做序列化及磁盘写入,对磁盘 I/O 资源、内存带宽、CPU 都有一定占用。
- 快照频繁可能影响性能:若触发条件配置得太激进,会导致频繁做快照,对系统影响不容忽视。
二、AOF:日志式持久化
2.1 原理
-
AOF(Append Only File)以 操作日志 的方式做持久化:Redis 在执行每一个会改变数据的写命令(例如
SET、DEL、HSET、LPUSH等)之后,把这条命令(以 Redis 协议格式)追加写入 AOF 日志文件。 -
重启时,Redis 读取 AOF 文件并依次执行这些命令,以重建出数据集状态。
-
为避免日志无限增长、重放命令开销过大,Redis 引入 AOF 重写(rewrite / compaction) 机制:在后台生成更简洁的日志(即只记录达到当前状态所需的最小命令)并替换旧日志。
-
对于何时把日志刷盘(fsync),Redis 提供三种策略,以平衡性能与数据安全性:
appendfsync always:每次写命令都同步刷盘(最安全,但最慢)appendfsync everysec:默认模式,每秒做一次 fsync(可能丢失最多一秒的数据)appendfsync no:不主动 fsync,由操作系统决定何时落盘(性能最好,但安全性最低)
Redis 文档中对这些策略及其折中有明确说明。Redis+1
2.2 实现流程(简化版)
-
客户端发起写命令 → Redis 在内存中执行
写命令(如
SET key value)先作用于内存数据结构 -
追加日志
Redis 将该命令以 Redis 协议格式(RESP 协议风格)追加写入 AOF 缓冲区 / 文件末尾
-
根据策略刷盘(fsync)
根据
appendfsync设置,决定是否立即 fsync、每秒 fsync 或不 fsync
-
日志增长 & 重写触发
随着命令增多,AOF 日志文件越来越大。为控制其体积、加速恢复,Redis 会在某些条件下触发 AOF 重写 (
BGREWRITEAOF):- fork 子进程bgrewriteaof
- 子进程根据当前内存中的 数据,生成对应的指令写入日志(即仅把当前状态所必需的操作写入)
- 子进程与父进程同时记录增量命令(在重写期间,父进程继续处理写命令且写入旧日志和重写子进程)
- 重写完成后,将新日志替换旧日志,清理旧文件
-
重启恢复
在服务器启动时,Redis 加载 AOF 文件,按顺序重放命令,恢复到最新状态

注意一个重要点 :Redis 的 AOF 机制是 "写后日志(write-after log)"------即先执行内存操作,再写日志;这样设计是为了避免记录无效命令(如果先写日志、再执行操作,那么如果操作失败,日志里可能留下不合法命令)。Redis+3ByteByteGo+3Redis+3
2.3 问题:如果在重写时,父/子线程一方要写数据,及具体如何实现?
首先要明确,子进程是如何共享父进程的数据的?
父进程在调用子进程时,操作系统会把父进程的页表 复制一份给子进程,页表中记录了虚拟地址和物理地址的唯一映射关系,两者虚拟空间不同,但物理空间相同,而且此时进程的权限为只读
编辑
那我要往里面写数据,就会破坏权限,此时cpu触发写保护中断,修改权限为可读写,然后触发写时复制
编辑
在fork 子进程bgrewriteaof,会使用一个AOF重写缓冲区
编辑
在子进程完成重写操作后,会向父进程发送信号,此时

所以总结一下,过程如下
- 当触发
BGREWRITEAOF(重写)时,Redis fork 出子进程重写 AOF; - 在重写期间,主线程继续处理写命令,并把这些命令 写入旧 AOF 文件(正常追加);
- 同时,主线程把这些写命令也记录到 重写缓冲区(diff buffer / rewrite buffer) ;
- 子进程完成重写后通知父进程,父进程把重写缓冲区中的命令追加写到新 AOF 的末尾,以保证新日志包含重写期间的写入;
- 最后,父进程做原子重命名 / 文件替换,切换到新的 AOF 日志,从此向新日志继续写命令。
2.4 优点
- 更高的数据安全性 / 更小数据丢失 :通常在
appendfsync everysec模式下,最多丢失 1 秒内的数据;在always模式下理论上可做到写命令后即持久化。 - 更可读 / 可审计:AOF 日志以命令格式记录(文本格式或类似 RESP 格式),可以人工阅读、审计、对比。
- 日志追加式写入:追加写入本身具有良好的顺序写入特性,不易破坏已有内容。
- 可修复 / 可跳过损坏部分 :AOF 日志如果部分损坏,可以用
redis-check-aof工具尝试修复或截断。
2.4 缺点 / 风险
-
恢复速度慢 / 重放开销大:重启时需要执行整个日志中的所有命令。随着日志变大,恢复时间会变长,尤其在写很多命令、变更频繁的场景下。
-
日志体积大:记录每条写命令,容易比 RDB 文件大很多,尤其在高写场景下。
-
重写开销:AOF 重写要 fork 子进程、做日志重写、磁盘 I/O,有时可能影响性能。
-
刷盘策略引入折中:
always策略带来显著的性能开销,写命令响应变慢;no策略牺牲安全性;- 默认的
everysec是折中方案,但允许最多丢失一秒的数据。
-
命令幂等性 / 再现性挑战:某些命令如果非幂等(例如 incr、append、时间相关命令等),重放时需确保重放逻辑和原始执行一致。
-
日志损坏风险:虽然追加写入相对安全,但文件中间若损坏或未完整写入可能导致重放失败,需要工具处理。
三、RDB vs AOF:深入对比与权衡
下面是详细的对比与权衡建议,这对你在实际选择持久化策略时非常重要。
| 维度 | RDB | AOF |
|---|---|---|
| 一致性 / 丢失风险 | 丢失可能为快照之间的时间窗口 | 最多丢失 0--1 秒(everysec),或更少(always) |
| 重启恢复速度 | 快(直接载入快照) | 慢(重放命令) |
| 磁盘 / 存储空间 | 较小(压缩快照) | 较大(日志全记录) |
| 对运行时的影响 | 较小,偶尔 fork 开销 | 写命令、fsync、重写可能带来持续开销 |
| 实现 / 复杂性 | 相对简单 | 较复杂(重写机制、日志管理、恢复重放等) |
| 可审计 / 可读性 | 不可读(是压缩二进制文件) | 可读 / 可审计 / 可编辑(慎用) |
| 适合场景 | 缓存为主、可容忍数据丢失、需要快速重启 | 业务数据为主、对数据安全性要求高、写操作频繁 |
Redis 官方文档和社区也推荐一种折中策略:RDB + AOF 混合(开启两者)------即用 RDB 做快照以加快恢复,用 AOF 记录写操作尽量减少丢失。
在 Redis 较新的版本中(如 7.x 之后),Redis 引入了一个混合持久化方式(Hybrid persistence / AOF-RDB 混合 / AOF preamble 机制),来折中两者优缺点。
3.1 混合方案工作原理
- 重写 / 生成新的 AOF 时,不仅写文本命令日志,还在文件开头加入一个 RDB 快照前缀(preamble) ,使得新 AOF 文件前半部分是 snapshot 二进制、后半部分是日志命令。
- 启动恢复时,Redis 先识别开头是 RDB 快照,先加载快照(高效),然后继续从日志部分重放命令(增量修正)。
- 这样做能显著缩短重放命令的时间,同时仍保留了 AOF 的增量记录能力。
- 在 Redis 7.0+ 中,这种混合方式通常默认启用(
aof-use-rdb-preamble配置项)
下面是混合方案文件结构示意:
diff
+----------------------+-----------------------------+
| RDB 快照二进制区块 | 增量命令日志 (AOF 部分) |
+----------------------+-----------------------------+
恢复过程:
- 识别 RDB 区块,加载快照
- 继续读取命令日志区块,按顺序重放
这种方式既保留了 fast snapshot 加载的优势,又控制了命令重放的压力。
5.2 优缺点与适用性
优点:
- 恢复速度快:大部分数据通过快照加载,命令重放范围小
- 日志体积受控:日志仅记录差异操作
- 数据安全性仍较好:保留操作日志,可确保最近变更不丢失
- 平衡性能与可靠性
缺点 / 注意事项:
- 实现复杂度更高
- 对配置和文件管理要求较高
- 在某些老版本或不支持混合方案的环境上无法使用
- 混合方式仍需考虑重写、刷盘策略、日志管理等
总之,如果你的 Redis 版本支持混合方案(7.x 以上),这是一个很值得考虑的折中策略。