从零起步学习Redis || 第七章:Redis持久化方案的实现及底层原理解析(RDB快照与AOF日志)
引言
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(确保写入磁盘)
-
-
原子替换
-
子进程成功写出、校验无误后,将临时文件重命名 / 移动 / 覆盖为最终的
dump.rdb
文件 -
子进程退出
-
-
父进程监控 / 完成
-
父进程得知快照完成(通过信号或状态)
-
后续根据下次触发条件再重复上述流程
-
-
重启恢复
-
启动 Redis 时,如果检测到已有的
dump.rdb
文件(路径、名称符合配置),则读取它,反序列化,将数据加载入内存 -
加载过程中 Redis 会构建内存结构(hash, list, set, zset 等),并完成校验
-
在这整个过程中,父进程在 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 支持良好。Redis+2memurai.com+2
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
配置项)
下面是混合方案文件结构示意:
+----------------------+-----------------------------+
| RDB 快照二进制区块 | 增量命令日志 (AOF 部分) |
+----------------------+-----------------------------+
恢复过程:
-
识别 RDB 区块,加载快照
-
继续读取命令日志区块,按顺序重放
这种方式既保留了 fast snapshot 加载的优势,又控制了命令重放的压力。
5.2 优缺点与适用性
优点:
-
恢复速度快:大部分数据通过快照加载,命令重放范围小
-
日志体积受控:日志仅记录差异操作
-
数据安全性仍较好:保留操作日志,可确保最近变更不丢失
-
平衡性能与可靠性
缺点 / 注意事项:
-
实现复杂度更高
-
对配置和文件管理要求较高
-
在某些老版本或不支持混合方案的环境上无法使用
-
混合方式仍需考虑重写、刷盘策略、日志管理等
总之,如果你的 Redis 版本支持混合方案(7.x 以上),这是一个很值得考虑的折中策略。