这篇笔记把 Redis 的数据落盘与复制链路放在一起梳理,核心是弄清楚 RDB、AOF、AOF 重写、全量复制、增量复制分别在什么时机发生,谁负责执行,哪里可能阻塞,哪里只保证最终一致。
内容以 Redis Open Source 的通用机制为主,尽量保留原始笔记结构,同时补上容易混淆的版本差异,尤其是 Redis 7.0 之后的 multi-part AOF,以及复制里 replication id、offset、backlog 的配合关系。
参考资料:
[TOC]
1.RDB快照持久化
RDB 的本质是某一时刻内存数据集的二进制快照。它适合备份、灾备、快速恢复,也常用于主从全量复制时给从节点传输初始数据。
1
2
3
4
# RDB 常见自动快照策略
save 900 1 # 900 秒内至少有 1 个键被修改
save 300 10 # 300 秒内至少有 10 个键被修改
save 60 10000 # 60 秒内至少有 10000 个键被修改
RDB文件逻辑结构
可以先按“理解版”记成下面这个顺序:
[魔数 “REDIS”] -> [4 字节 ASCII 版本号] -> [可选辅助元数据 AUX] -> [一个或多个数据库片段] -> [EOF 0xFF] -> [8 字节校验和]
魔数(Magic Number): 文件类型标识
- 位置:文件最开头,占 5 字节。
- 内容:固定为字符串
REDIS的 ASCII 编码。 - 作用:Redis 加载文件时先校验魔数,确认这是合法的 RDB 文件。
版本号(RDB Version): 文件格式版本标识
- 位置:魔数之后,占 4 字节。
- 内容:不是二进制大端整数,而是 4 个 ASCII 字符,例如
0009、0010。 - 作用:不同版本的 RDB 在 opcode、编码方式、模块扩展数据上可能存在差异,Redis 会按版本选择解析逻辑。
可选元数据(Optional Metadata / AUX): 文件辅助信息
- 位置:通常出现在版本号之后、正式数据区之前,也可能穿插少量辅助 opcode。
- 内容:常见包括 Redis 版本、创建时间、复制相关信息、内存使用提示等。
- 作用:主要用于兼容性、调试和运维观察,不是“键值数据本体”。
数据库数据区(Database Data): 核心数据存储
- 这是 RDB 文件中体积最大的部分。
- Redis 会按数据库维度写入数据,常见会看到
SELECTDB、RESIZEDB、过期时间相关 opcode,再跟上具体键值对。 - 值对象会按类型使用不同编码方式,例如字符串、列表、哈希、集合、有序集合的序列化格式并不相同。
EOF 标记: 数据区结束标识
- 位置:所有数据库数据写完后,占 1 字节。
- 内容:固定为
0xFF。 - 作用:告诉解析器核心数据区已经结束,后面只剩校验信息。
校验和(Checksum): 文件完整性校验
- 位置:文件尾部,占 8 字节。
- 内容:默认是对前面全部内容计算出的 64 位校验值,常见实现是 CRC64。
- 作用:Redis 加载 RDB 时会重新计算校验值,不一致则认为文件可能损坏。
注意:如果只是为了理解持久化机制,不必死记每个 opcode;更重要的是知道 RDB 是“面向恢复的二进制快照”,不是命令日志。
save 命令:同步阻塞,不适合在线业务
执行逻辑: SAVE 由 Redis 主线程直接遍历内存数据并写出 RDB。执行期间事件循环被占住,Redis 不能正常处理新的客户端请求。
阻塞表现: 更准确的说法不是“拒绝所有请求”,而是客户端请求通常会排队等待,超时后才表现为业务侧报错。
典型场景: 只适合调试、维护窗口或非常小的数据集,不建议在生产流量下手工执行。若要在关闭前强制落盘,更常见的表达是使用 SHUTDOWN SAVE。
bgsave 命令:主线程继续服务,子进程负责落盘
执行逻辑: BGSAVE 由主进程调用 fork() 创建子进程,子进程负责生成 RDB 文件,主线程继续处理命令请求。
潜在阻塞点: 真正容易抖动的时刻是 fork() 本身。数据集越大、页表越大、内存碎片越明显,fork() 的暂停时间越容易被放大。
COW 机制影响: 子进程拿到的是 fork 时刻的内存视图。之后主线程若修改某个内存页,会触发写时复制(COW),让父子进程各自持有不同页副本。因此高写入流量下,快照期间的额外内存占用可能明显上升。
RDB相关并发限制
更准确的记忆方式是:同一时刻不会并发运行多个重型后台持久化子进程。
- 已有
BGSAVE在执行时,再发起BGSAVE会被拒绝。 SAVE是同步命令,执行期间主线程本身就被占住,因此不存在“两个 SAVE 并发跑”的实际意义。BGSAVE与BGREWRITEAOF都依赖fork()子进程,Redis 会避免让多个这类后台子进程同时重叠运行,以控制 CPU、内存和 IO 压力。
RDB文件生成过程

SAVE 是主线程操作,所以会阻塞新的读写请求。下面重点看 BGSAVE 的过程。
1、主进程准备:fork 子进程
2、子进程生成 RDB 数据:遍历与序列化
3、主进程并发处理:写时复制(COW)保障数据一致性
- 极端情况下,如果快照期间绝大多数内存页都被改写,额外内存占用可能接近原数据集规模。
4、子进程完成写入,原子替换目标文件
- 子进程先写临时文件,完成后再通过原子重命名替换旧 RDB 文件。
- 子进程退出后,操作系统会向主进程发送
SIGCHLD,主进程据此回收子进程资源并更新持久化状态。
5、主进程清理资源与更新状态
RDB补充知识点
LASTSAVE可以查看最近一次成功生成 RDB 的时间戳。stop-writes-on-bgsave-error yes是常见保护项,避免后台快照持续失败时主节点还继续接收写入。- RDB 很适合备份和快速重启,但不适合拿来承诺“几乎不丢数据”;它本质上是时间点快照,不是逐条写入日志。
2.AOF持久化
AOF 的核心思路不是“保存某一时刻结果”,而是记录会修改数据集的写命令。Redis 重启后通过重放这些命令来恢复数据。
1
2
3
appendfsync always # 每次写命令后都执行 fsync
appendfsync everysec # 每秒 fsync 一次,默认策略
appendfsync no # 不主动 fsync,由操作系统决定何时刷盘
可以把 AOF 的写入链路拆成四步:
- 执行写命令,先修改内存中的真实数据。
- 把对应协议内容追加到 AOF 缓冲区(
aof_buf)。 - 主线程调用
write(2),把数据写到 AOF 文件描述符,通常先进入内核页缓存。 - 再由
appendfsync策略决定何时真正fsync到磁盘。
问题一:Redis执行命令成功,但是在写AOF之前宕机
数据可能丢失。
更细一点说,写命令“执行成功”只代表内存状态已经更新,不等于已经持久化成功。崩溃点不同,风险窗口也不同:
- 如果命令刚改完内存,还没追加到
aof_buf,那么这条写入肯定无法通过 AOF 恢复。 - 如果已经写入
aof_buf,但还没write到内核缓冲区,也可能丢。 - 如果已经
write到页缓存,但还没fsync,那么是否能保住,取决于内核何时真正落盘;这也是everysec和no会有数据丢失窗口的原因。
问题二:RDB文件是fork子进程生成,AOF刷盘是由谁操作
AOF 的 命令执行 和 AOF 追加写入 主要由主线程负责;fsync 是否放到后台线程,要看 appendfsync 策略。
appendfsync always:主线程执行完一批写命令后,会完成 AOF 写入并显式fsync,然后再向客户端返回结果。延迟最高,但持久化窗口最小。appendfsync everysec:主线程仍负责把数据写到 AOF 文件,fsync通常由后台线程每秒执行一次。正常情况下吞吐和延迟更平衡,通常最多丢 1 秒左右数据。appendfsync no:主线程只负责写入内核缓冲区,不主动fsync,真正刷盘交给操作系统。它通常最快,但并不等于“绝对无阻塞”,因为内核缓冲区压力大时,write也可能卡住主线程。
一个容易记错的点:
everysec不是“主线程完全不碰磁盘”,而是主线程仍然要做 AOFwrite,只是把最重的fsync尽量移给后台线程。
appendfsync 三种策略的权衡
always- 优点:持久化窗口最小,客户端收到成功响应时,这批写入通常已经过
fsync。 - 缺点:延迟最高,不适合高吞吐写场景。
- 优点:持久化窗口最小,客户端收到成功响应时,这批写入通常已经过
everysec- 优点:默认策略,性能与可靠性平衡较好。
- 缺点:遇到进程崩溃、机器掉电时,通常仍可能丢失最近 1 秒左右数据;如果底层 IO 很忙,也可能出现短暂抖动。
no- 优点:省掉主动
fsync,写入开销最低。 - 缺点:数据安全最弱,丢失窗口取决于内核刷盘时机,实践里可能远大于 1 秒。
- 优点:省掉主动
AOF补充知识点
- AOF 比 RDB 更耐久,但通常文件更大、恢复速度也可能更慢。
- Redis 4.0 之后,AOF 重写结果可以包含 RDB preamble,这样恢复更快;因此“重写后的 AOF 文件一定全是可读命令文本”并不总成立。
- AOF 不是备份系统。它解决的是“实例重启后的恢复”,真正的灾备仍然需要离机备份和多副本。
3.AOF重写
AOF 会随着写命令不断增长,而很多历史命令对“恢复当前状态”其实已经没有意义。
例如一个 key 被反复 SET、INCR、DEL 很多次,最终只需要保留能构造出“当前最终状态”的那组最小命令或快照。AOF 重写的目标就是把这份“当前状态表达”重新生成出来。
1
2
3
4
5
# 当前 AOF 文件体积比上次重写后体积增长了 100% 时,满足增长条件
auto-aof-rewrite-percentage 100
# 当前 AOF 文件至少达到 64MB 时,满足最小体积条件
auto-aof-rewrite-min-size 64mb
Redis 会周期性检查是否满足自动重写条件:
- 当前 AOF 文件已经达到最小体积门槛。
- 相比上次重写后的基线体积,增长比例已经达到阈值。
若同时满足,就会自动触发 BGREWRITEAOF。
首次重写更适合理解为:主要先看
auto-aof-rewrite-min-size,因为还没有稳定的历史基线,增长比例条件不会成为真正门槛。
AOF 重写并不是去“裁剪旧 AOF 文件”,而是遍历当前内存数据集,重新生成一份能够恢复当前状态的新持久化结果。
AOF重写过程:Redis < 7.0 的经典模型
下面这套“双缓冲区 + 临时文件 + rename”描述,更贴近 Redis 7.0 之前的单文件 AOF 机制。
1、主进程 fork 子进程
- 子进程继承
fork时刻的内存快照,依赖 COW 共享页面。
2、子进程生成新的 AOF 临时文件
- 子进程遍历当前内存数据。
- 为每个 key 生成更精简的恢复命令。
- 将这些内容写入临时 AOF 文件。
3、主进程处理新命令(双缓冲区机制)
重写期间主线程仍然要处理新的客户端写命令。为了保证新命令不丢失,主线程会同时做两件事:
- 把新命令继续追加到旧 AOF 文件,保证旧文件始终可用。
- 把新命令写入 AOF 重写缓冲区,等子进程完成后再补到新文件末尾。
4、主进程合并新命令并原子替换旧文件
- 子进程写完临时文件后退出。
- 主进程收到
SIGCHLD后,把重写缓冲区中的增量命令追加到临时新文件。 - 主进程调用
rename()原子替换旧 AOF 文件。 - 旧文件被新文件顶替,重写完成。
Redis 7.0+ 的变化:multi-part AOF
Redis 7.0 开始,AOF 不再一定只有一个 appendonly.aof 文件,而是拆成了:
- base AOF:重写时生成的基线文件,可能是 AOF 格式,也可能带 RDB preamble。
- incremental AOF:重写过程中以及之后产生的增量文件。
- manifest:记录当前哪些 base / incremental 文件有效的清单文件。
这一版机制的核心变化是:
- 父进程在重写开始后会打开新的增量 AOF 文件继续接收写入。
- 子进程负责生成新的 base AOF。
- 完成后父进程原子切换 manifest,而不是只做“单个 appendonly.aof 文件的 rename 覆盖”。
所以如果面试或复习时提到“AOF 重写最后一定是 rename 覆盖旧 appendonly.aof”,最好补一句:这是 Redis 7.0 之前更典型的描述;Redis 7.0+ 需要结合 multi-part AOF 来理解。
问题一:AOF重写时,怎么保证新来的指令最终一致
核心靠两层保障:
- 主线程不会停止处理新写命令。
- 这些新命令会被额外保存下来,并在切换新 AOF 生效前补进去。
在 Redis < 7.0 中,这通常体现为 rewrite buffer;在 Redis 7.0+ 中,则体现为新的 incremental AOF 与 manifest 切换。
问题二:AOF重写失败怎么办
重写失败不会直接破坏当前可用数据,原因是:
- 重写期间旧 AOF 仍持续接收新的写命令。
- 新文件或新 manifest 未切换成功前,旧的持久化链路仍然有效。
- 因此失败后通常只是“本次重写没完成”,而不是“实例马上失去恢复能力”。
4.主从同步
从 Redis 2.8 开始,复制协议的重要变化之一是
PSYNC与部分重同步(partial resynchronization)。
重要概念:replication id、复制偏移量、复制积压缓冲区、全量复制、增量复制。
先把三件关键状态记住
- replication id(replid):标识一条复制历史分支。主节点发生故障转移后,新的主节点通常会产生新的 replid。
- 复制偏移量(offset):表示复制流已经处理到哪里,本质上是字节流位置。
- 复制积压缓冲区(replication backlog):主节点保留最近一段复制流内容的环形缓冲区,用于断连后的部分重同步。
只有把这三者放在一起,才能真正解释“为什么有时能增量同步,有时必须全量同步”。
主从关系建立
- 从节点启动后,或执行
REPLICAOF(旧命令名是SLAVEOF)后,会主动与主节点建立 TCP 连接。 - 如果主节点开启了认证,从节点需要通过
masterauth,或者基于 ACL 的masteruser/masterauth完成认证。 - 连接建立后,双方进入复制握手阶段,后续才判断走全量同步还是部分重同步。
首次同步(全量复制)
当从节点第一次接入主节点,或者断连时间过长导致 backlog 已经覆盖掉缺失数据时,就会触发全量复制。
1、从节点发送同步请求
- 首次连接时,可以把它理解为从节点发送
PSYNC ? -1,表示“我没有可复用的复制历史,请给我一份完整数据”。
2、主节点准备全量数据
- 主节点执行一次
BGSAVE,由子进程生成 RDB 快照。 - 生成 RDB 的同时,主节点继续处理新的写命令,并把这些增量变更写入复制积压缓冲区。
- 主节点在全量复制期间通常仍可继续对外服务,这也是 Redis 复制在主节点侧基本非阻塞的原因。
3、主节点发送 RDB 文件
- 子进程生成 RDB 后,主节点把 RDB 内容通过复制连接发送给从节点。
- 发送期间若还有新的写命令到来,这些命令仍继续进入 backlog,等待稍后补发。
4、从节点加载 RDB 文件
这里要比“先清空内存再加载”说得更细一点:
- 从节点在接收全量数据期间,在很多配置下仍可以继续对外提供旧数据读服务。
- 但当真正切换到新数据集时,旧数据需要被丢弃,新 RDB 需要被加载到内存。
- 这个切换窗口会阻塞从节点请求;数据越大,阻塞越明显。
也就是说,从节点不是从一开始就完全不可用,而是在最终替换旧数据并加载新快照的窗口里会明显阻塞。
5、主节点补发 backlog 中的增量命令
- 从节点加载完 RDB 后,主节点再把 RDB 生成和传输期间积压在 backlog 中的写命令继续发给从节点。
- 从节点执行这些命令后,就追平到了主节点当前状态。
6、同步完成:进入稳定复制状态
- 从节点完成全量同步后,后续进入持续命令流复制阶段。
- 从节点默认通常处于只读模式,即
replica-read-only yes。
增量复制
全量复制完成后,主从进入持续的命令传播阶段。主节点把后续写命令不断推给从节点,从节点顺序执行这些命令。
1、主节点实时传播写命令
- 主节点执行写命令后,除了更新自身内存,也会把这条命令对应的复制流发送给所有从节点。
- 这个过程默认是异步复制,主节点不会为了等待所有从节点确认而阻塞当前写请求。
2、从节点执行命令并更新偏移量
- 从节点接收复制流后顺序执行。
- 主从两端都会推进自己的复制偏移量,用于判断已经同步到什么位置。
3、心跳机制维持连接
- 从节点会周期性向主节点发送
REPLCONF ACK <offset>,告诉主节点自己已经处理到哪个偏移量。 - 主节点可以根据 ACK 判断从节点落后程度,并结合 backlog 决定是否还能做部分重同步。
- 如果长时间收不到对端心跳,连接会被判定超时并重连,这个阈值通常和
repl-timeout等配置有关。
断连后同步
如果主从链路暂时断开,重连后优先尝试部分重同步,而不是一上来就全量复制。
1、从节点发起重连请求
- 从节点重连时,会带上自己记住的 replid 与 offset,请求继续同步缺失的那一段复制流。
2、主节点判断同步方式
- 可以增量同步: 主节点发现从节点带来的 replid 仍然可识别,且缺失的那段数据还在 replication backlog 中,那么只补发缺失命令即可。
- 必须全量同步: 如果 replid 已经不匹配,或者缺失数据已经被 backlog 覆盖掉,就只能重新做一次全量复制。
这也是为什么只记 offset 不够,还必须同时理解 replid 和 backlog。
核心注意事项
- Redis 复制默认是异步的,所以“主从同步完成”并不等于强一致,只能保证最终一致。
WAIT命令可以降低主节点刚写入就故障时的数据丢失概率,但它也不等于把 Redis 变成强一致数据库。- backlog 大小非常关键。写流量大、网络抖动频繁时,backlog 太小会让部分重同步频繁退化成全量复制。
- 全量复制对主节点、从节点、网络都很重,尤其是大内存实例,要重点关注
fork延迟、网络带宽和从节点加载窗口。
问题:增量同步涉及RDB吗,从节点会不会阻塞读操作
增量同步本身不涉及重新传输和加载 RDB。
- 部分重同步 的核心是补发缺失的复制命令流,而不是重新发送快照文件。
- 因此不会出现“像全量复制那样重新加载 RDB”的大阻塞窗口。
- 但从节点执行复制命令本身仍然在主线程完成,高写流量下读请求依然可能感知到轻微延迟,只是通常比全量同步小得多。
5.选型建议与常见误区
- RDB 适合备份、灾备、快速重启。 如果更关心恢复速度和备份体积,RDB 很合适。
- AOF 适合更高的数据耐久要求。 如果不能接受“丢最近几分钟数据”,通常至少会考虑 AOF
everysec。 - 线上常见做法是 RDB + AOF + 副本。 RDB 提供快照与备份便利,AOF 提供更小的数据丢失窗口,副本提供高可用,但三者职责并不相同。
- 复制不等于持久化。 只有主从复制而没有落盘,机器整体掉电时仍可能一起丢数据。
- 持久化也不等于备份。 无论是 RDB 还是 AOF,本质上都更偏“实例恢复”,真正的灾备仍需要把文件异地保存。
- 版本差异必须带着记。 讨论 AOF 重写时,如果不说明 Redis 7.0+ 的 multi-part AOF,很容易把旧版本经验当成通用结论。