Theory · Redis · Persistence

RDB / AOF / 混合持久化

fork 快照 + 追加日志 + 两全其美的混合 —— 数据安全、性能、恢复速度的三角权衡

RDB

fork + 写时复制的二进制快照:文件紧凑、恢复快,代价是快照间隙的数据丢失窗口

AOF

写后命令日志:everysec 最多丢约 2 秒;7.0 Multi-Part AOF 用 base+incr+manifest 根治重写顽疾

混合

aof-use-rdb-preamble:重写时 RDB 打头、增量 AOF 收尾——体积与恢复速度兼得,生产默认

持久化是 Redis 面试的必考三分之二:RDB、AOF、混合。这份 deck 的主线:先搞清"为什么要持久化"——不只是宕机恢复,还是主从全量同步的数据源;然后按 RDB 的 fork 与 COW、AOF 的写后日志与 fsync、重写的演进(老 aof_rewrite_buf 到 7.0 三件套)、混合持久化四块展开,最后给对比表和生产选型。所有默认值都对照 8.x redis.conf 核过,包括 save 三个默认点、everysec 的两秒机制、auto-aof-rewrite 的两个参数。

Why Persist

为什么持久化:不只是"怕宕机"

用途一:宕机恢复

内存数据库,进程退出数据即消失;持久化文件让重启后能重建数据集。官方文档开门见山:没有持久化的 Redis 有时被当作纯缓存,但大多数场景需要它。

用途二:主从全量同步的数据源

主从第一次同步 / 断线过久无法增量复制时,主库执行的正是 bgsave 生成 RDB 发给从库全量加载。且官方明确:RDB 在副本上支持重启/故障转移后的部分重同步(RDB 里存了 replication id/offset)——详见 replication deck。

与持久化联动的机制关系
fork / 写时复制(COW)RDB 与 AOF 重写都靠 fork 子进程做重活——持久化的性能问题本质是 fork 与 COW 的内存问题(第 4/6 页)
bio 后台线程AOF 的 everysec fsync 跑在 BIO_AOF_FSYNC 线程(thread-model deck),磁盘慢不直接卡主线程
内存淘汰(maxmemory)复制/AOF 缓冲不计入淘汰判定(mem_not_counted_for_evict)——官方建议给副本/持久化实例预留内存余量
主从复制互斥Redis ≥2.4 保证 BGSAVE 与 AOF 重写不同时跑:两个重型磁盘 IO 子进程不同时上
为什么持久化有两个答案:一是宕机恢复,这是常识;二是主从全量同步用的 RDB 就是 bgsave 产物,而且 RDB 里带着 replication id,副本重启后还能做部分重同步,这是很多人不知道的第二用途。另外记住两条联动:持久化的性能问题本质是 fork 和写时复制的内存问题;复制缓冲不计入 maxmemory 淘汰判定,所以持久化实例要预留内存。还有条互斥规则:BGSAVE 和 AOF 重写不会同时跑,官方从 2.4 起就这么设计。

Overview

两种哲学:快照 vs 命令日志

RDB:某一时刻的全量快照

二进制、单文件、时间点一致。像"存档":恢复 = 直接读档,速度快。存档之间发生的事不在档里——快照间隙宕机就丢这段。dump.rdb 由 save 点控制自动生成。

AOF:每条写命令的追加日志

像"操作账本":重启 = 从头重放账本重建状态。记录全 → 最多丢一秒(everysec);但账本越记越长,需要定期"重写"压缩成当前状态的最短命令集。

官方四种选项说明
RDB定时快照;适合备份/灾备/快速恢复
AOF追加命令日志,更耐久;官方提醒:不建议只开 AOF——定期 RDB 快照对备份、快速重启、防 AOF 引擎 bug 都有价值
无持久化纯缓存场景(save "" + appendonly no)
RDB + AOF 同开想要 PostgreSQL 级数据安全就都开;重启时 AOF 优先加载(保证最完整)
答题框架:"快照式全量、日志式增量;RDB 赢在体积和恢复速度、输在丢数据窗口;AOF 赢在耐久、输在体积与重写开销——混合持久化把两者缝在一起。"
先立框架:RDB 是快照哲学,AOF 是日志哲学,快照赢在紧凑和恢复速度、输在丢失窗口,日志赢在耐久、输在体积和重写。官方给了四个选项,重点背两句:一是不建议只开 AOF,官方原话说定期 RDB 快照对备份和快速重启都重要;二是两个都开时重启优先加载 AOF,因为它保证最完整。想要 PostgreSQL 级别的数据安全,官方建议就是两个都开。

RDB · SAVE / BGSAVE

RDB 原理:fork 子进程 + 写时复制

BGSAVE 的 fork 与写时复制内存布局 fork 瞬间复制页表,父子进程共享物理内存页;父进程写某共享页时内核复制该页供父进程修改,子进程继续读旧页做快照;子进程写临时 RDB 后原子 rename 替换旧文件。 fork:仅复制页表(共享页) 写 P3 → COW 复制 完成后 rename 父进程(继续服务) P1 P2 P3' P4 子进程(只读快照) P1 P2 P3(旧) P4 P3'(新副本) 父进程后续写都落在自己的新副本上,子进程视角仍是 t0 时刻数据 —— 快照的时间点一致性由此而来 写临时文件 temp-<pid>.rdb 写完 rename(2) 原子替换 dump.rdb fork 瞬间阻塞 = 页表复制耗时 官方:数据集大 + CPU 弱时可达 1 秒级 COW 期间父进程写越多 → 内存放大越多 最坏(全部页被写)≈ 数据集翻倍 SAVE(同步):主线程自己写 RDB 全程阻塞、生产禁用;只用于调试
BGSAVE 的完整机制:fork 出子进程,fork 本身只复制页表不复制内存,父子共享物理页;父进程随后写某个共享页时,内核复制那一页给父进程改,子进程读到的还是 t0 的旧页,这就是快照时间点一致的原理。三个要点:fork 的阻塞等于页表复制耗时,官方说大实例弱 CPU 能到一秒;COW 期间父进程写得越多内存放大越多,最坏翻倍;SAVE 是主线程同步写盘,生产禁用。子进程最后写临时文件再 rename 原子替换,所以 dump.rdb 永远不会是半成品。

RDB Config · Pros & Cons

RDB 的触发与官方优缺点清单

# redis.conf(8.x 默认三个 save 点)
save 3600 1    # 1 小时内 ≥1 次修改
save 300 100   # 5 分钟内 ≥100 次修改
save 60 10000  # 1 分钟内 ≥10000 次修改

# 相关参数:
dbfilename dump.rdb     # 文件名
dir ./                  # 存放目录
rdbcompression yes      # LZF 压缩
rdbchecksum yes         # CRC64 校验
save ""                 # 完全关闭自动快照
触发方式说明
save 点(自动)满足"N 秒 M 次修改"即 BGSAVE(serverCron 检查 dirty 计数)
手动BGSAVE 异步 / SAVE 同步阻塞;SHUTDOWN 且有 save 点时关闭前也会保存(kill -9 除外)
主从全量同步时主库执行 BGSAVE 生成 RDB 发送给从库
互斥AOF 重写进行中时 BGSAVE 被拒绝/排队(≥2.4)
RDB官方口径(persistence 文档)
优点单文件紧凑、适合备份/异地灾备/S3 归档;父进程除 fork 外不做任何磁盘 IO;大 dataset 重启比 AOF 快;副本重启/切换后可部分重同步
缺点快照间隙宕机丢"最近几分钟"数据;大 dataset 频繁 fork 耗时可观(毫秒级,极端可到 1 秒级),且 COW 放大内存
一句话:"RDB = 用数据丢失窗口换性能与恢复速度。缓存类业务用 RDB 甚至不持久化都行;要保证数据安全的业务,RDB 只能当备份手段,不能当唯一持久化。"
RDB 触发的默认三个 save 点要背:一小时一次改、五分钟百次改、一分钟万次改,serverCron 按脏页计数检查。注意 SIGTERM 正常关闭时如果有 save 点也会落一次 RDB,kill -9 不会。优缺点按官方口径背:优点是单文件紧凑适合备份异地灾备,父进程除了 fork 不碰磁盘,恢复快,还支持副本重启后的部分重同步;缺点是快照间隙丢数据、fork 与 COW 的代价。这个"部分重同步"是 RDB 在主从场景的隐藏加分项。

fork / COW in Production

生产视角:fork 是持久化的第一性能杀手

fork 瞬时阻塞 = 复制页表

fork 复制的是页表不是内存:10GB 实例 ≈ 268 万个 4KB 页 ≈ 21MB 页表需要内核复制。官方文档:数据集大且 CPU 不强时,fork 可能让服务停摆"几毫秒甚至一秒"。实例越大、fork 越贵——这就是"单实例别超 10GB"的依据之一。

COW 内存放大与 THP 陷阱

BGSAVE 期间父进程每个写操作都可能触发页复制——写入热点越散,放大越明显,最坏逼近数据集翻倍,可能触发 OOM killer。另一个大坑:透明大页 THP 把页从 4KB 变 2MB,COW 粒度放大 512 倍,一次写复制 2MB——Redis 官方启动日志直接建议 echo never > /sys/kernel/mm/transparent_hugepage/enabled

手段作用
关 THP(madvise/never)COW 粒度回到 4KB,fork 后写放大可控——生产实例标配
预留内存余量机器内存 ≥ maxmemory × 2 再开持久化(COW + 复制缓冲都要空间);云上监控 RSS 而非 used_memory
低峰重写/快照调整 save 点与 auto-aof-rewrite 时机;bigkeys 写入热点集中也能减少 COW 页数
控制实例规模单实例数据集上限(官方建议参考 10–20GB 量级),或上集群分片——fork 时长是线性于页表规模的
这页是生产向的重点。fork 阻塞的实质是复制页表,十 GB 实例大概 21MB 页表,内核单线程复制,弱机器能停一秒——所以单实例规模有上限。COW 的坑两个:写热点越散内存放大越狠,可能被 OOM killer 干掉;更大的坑是透明大页 THP,把复制粒度从 4KB 放大到 2MB,一次写就复制 2MB,Redis 启动日志都会警告,生产必须关。运维三件套:关 THP、内存按 maxmemory 两倍预留并盯 RSS、低峰安排快照和重写。

AOF · Write-after-log

AOF 原理:写后日志 + aof_buf + 事件循环 flush

为什么是"写后日志"

命令先执行成功、再追加到 AOF(传统数据库 WAL 是先写日志)。好处:① 不阻塞当前写操作;② 日志里只有成功执行的命令,不会记录语法错误的命令——重启重放天然安全。代价:命令执行完到真正落盘之间有一个丢失窗口(下一页的 fsync 策略就在管这个窗口)。

追加链路(write 与 fsync 分离)

命令执行完 → 追加进 aof_buf(redisServer.aof_buf)→ 每轮事件循环 beforeSleep 统一 write() 到内核页缓存 → fsync() 何时执行由 appendfsync 决定(everysec 时交给 BIO_AOF_FSYNC 后台线程)。write 不等于落盘:write 只进 OS 缓冲,fsync 才到磁盘。

AOF 文件内容RESP 文本协议记录(人可读)
示例*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n —— 与客户端协议同格式,7.0 起 base 文件甚至可以是 RDB 格式(混合)
记录什么命令所有"改变了数据集"的写命令(含 DEL/EXPIRE/UNLINK);7.0 起集合/哈希被删空的隐式删除(如 HDEL 最后一个 field)会以显式 DEL 传播进 AOF 与从库——保证主从不出现"幽灵空 key"
恢复启动时建伪客户端(fake client)从头重放——没有 Lua/事务上下文,纯命令重放
面试要点:"AOF 的三个工程词:写后日志(防坏命令进日志)、aof_buf(攒批 write)、fsync 策略(管丢失窗口)。把 write 和 fsync 分开说,才算懂 AOF。"
AOF 是写后日志,和数据库 WAL 相反:命令先执行成功再记。好处有两个:不阻塞写操作,日志里永远不会有失败命令,重放天然安全。链路上三个环节要分清:命令先进 aof_buf 攒批,beforeSleep 时统一 write 到内核缓冲,fsync 才是真正落盘,由 appendfsync 决定节奏。文件内容就是 RESP 文本协议,人可读,甚至能手工删掉最后一条误操作的命令再重启来"反悔"。恢复时建一个伪客户端从头重放,这句话面试报出来很加分。

appendfsync always / everysec / no

三种 fsync 策略:丢失窗口的精确口径

策略机制丢失窗口性能口径(官方)
always每批命令追加后立即 fsync(多客户端/管道的命令合并成一次 write + 一次 fsync,组提交≈ 0(最多丢"正在执行未追加"的那条)Very very slow, very safe(官方原话)
everysec(默认)write 每轮循环都做;fsync 交给后台线程每秒一次;上次 fsync 未完成时推迟 flush,推迟超 2 秒才强制执行官方口径 1 秒;机制最坏 ≈ 2 秒(推迟写的上限)fast enough(2.4 起接近快照性能)
no只 write 不主动 fsync,节奏交给操作系统(Linux 通常 30 秒刷一次)不确定(可能 30s 级)最快也最不安全(the faster and less safe)
everysec 的写推迟与 2 秒上限机制 时间轴上每轮事件循环 write 到内核缓冲,后台线程每秒 fsync;当上一次 fsync 未完成时新 flush 被推迟并记录推迟起点,推迟满 2 秒则强制同步执行 write,因此最坏丢失窗口约 2 秒。 t0 t0+1s t0+2s t0+3s fsync① 完成 fsync② 开始 · 磁盘繁忙未完成 推迟满 2s → 强制 flush 这期间的新 write 被推迟 机制:flushAppendOnlyFile 发现上次 fsync 未完成 → 记下推迟起点,先不写; 推迟超过 2 秒(aof_flush_postponed_start)才强制 write —— 宕机时 aof_buf 里滞留的命令 最多约 2 秒 → 这就是"everysec 丢约 1~2 秒"的精确来源(官方口径:you may lose 1 second) aof_buf 每轮 flush 成功后即清空重攒;write 成功 ≠ fsync 完成
三种 fsync 策略按官方口径背:always 很慢很安全,注意它有组提交,一批命令合并成一次 write 加一次 fsync;no 最快最不安全,交给操作系统大概三十秒刷一次;默认 everysec 要能讲出两秒的机制——fsync 在后台线程做,发现上次 fsync 没完成就推迟这轮 write 并记下推迟起点,推迟满两秒才强制执行。所以官方说丢一秒,机制最坏约两秒,宕机时丢的是 aof_buf 里没写出去的部分。面试里能把 write 和 fsync 分开、把两秒推迟机制讲出来,就超过绝大多数候选人了。

AOF Rewrite · BGREWRITEAOF

AOF 重写:为什么必须重写 < 7.0 的痛

为什么账本要压缩

INCR counter 执行 100 次,AOF 里 100 条命令只对应内存里 1 个 key——99 条是废账。重写 = fork 子进程按当前内存状态生成"重建数据集的最短命令集"。官方保证重写安全:期间继续追加旧文件,新文件就绪后才切换。

< 7.0 的重写机制(三宗罪)

子进程写新 AOF 期间,父进程把新写命令同时存进内存缓冲 aof_rewrite_buf,结束后补写进新文件。官方文档列的代价:①重写期有写入时 AOF 可占用大量内存(缓冲无上限,实战能把实例打爆);②写命令落盘两次(旧文件一次、缓冲一次);③结束时父进程可能被补写 + fsync 冻结

重写的自动触发说明
auto-aof-rewrite-percentage 100体积比上次重写后增长 ≥100% 就触发
auto-aof-rewrite-min-size 64mb且当前体积 ≥64MB(两个条件同时满足)
手动BGREWRITEAOF;与 BGSAVE 互斥排队(≥2.4)
答题主线:"重写解决的是日志膨胀;<7.0 的方案用内存缓冲补增量,换来内存翻倍风险与结尾冻结;7.0 的 Multi-Part AOF 用'新 INCR 文件 + manifest 原子切换'根治——这是 AOF 面试的主战场。"
重写的原因一句话:账本膨胀,一百条 INCR 只对应一个 key。重写就是 fork 子进程按当前内存生成最短命令集。老版本的重写机制要能讲出三宗罪:一是 aof_rewrite_buf 内存缓冲没有上限,重写期写入一多能把实例打爆;二是命令要写两遍,旧文件一遍缓冲一遍;三是重写结束时主线程要补写缓冲并 fsync,可能被冻结。官方文档原文就列了这三条。自动触发两个参数一起记:百分比 100 加最小 64MB,体积翻倍才重写。

Redis 7.0 · Multi-Part AOF

Multi-Part AOF:base + incr + manifest 三件套

Multi-Part AOF 的目录结构与重写流程 appendonlydir 目录下有 base 快照文件、多个 incr 增量文件与 manifest 清单;重写时父进程打开新的 INCR 文件继续写,子进程生成新 BASE,完成后父进程构建临时 manifest 并原子 rename 生效,旧文件标记 HISTORY 待删。 manifest 登记 持续追加 appendonlydir/(appenddirname,默认 appendonlydir) appendonly.aof.1.base.rdb BASE:重写时刻的快照(RDB 或 AOF 格式,至多 1 个有效) appendonly.aof.1.incr.aof INCR:自 BASE 以来的增量命令(可多个,顺序追加) appendonly.aof.2.incr.aof 重写期间父进程直接打开的新 INCR —— 增量不再进内存缓冲 appendonly.aof.manifest 清单:登记当前生效的 base/incr(seq + type + 文件名) manifest 内容示意(真实格式) seq type filename 1 base appendonly.aof.1.base.rdb 1 incr appendonly.aof.1.incr.aof 2 incr appendonly.aof.2.incr.aof 替换生效的时机:重写完成后,父进程生成"临时 manifest", 原子 rename 覆盖 —— 要么旧三件套、要么新三件套,不存在中间态。 被替换的旧文件标记 HISTORY,随后清理。 重写失败自动重试且速率递减(rewrite limiting)——不会 因反复失败刷出无数 INCR 文件。 重写五步:① 父进程打开新 INCR 继续写 ② 子进程生成新 BASE ③ 子进程完成、通知父进程 ④ 父进程用新 BASE + 新 INCR 构建临时 manifest 并持久化 ⑤ 原子替换 manifest,清理 HISTORY 文件
7.0 的 Multi-Part AOF 把单文件拆成三件套:BASE 是重写时刻的快照,可以是 RDB 格式;INCR 是增量命令,可以有多个;manifest 是清单,登记当前生效的文件组合。重写流程五步背下来:父进程先打开新 INCR 直接写盘,子进程生成新 BASE,完成后父进程构建临时 manifest,原子 rename 生效,旧文件标记 HISTORY 清理。三个关键收益:aof_rewrite_buf 没了,内存问题根治;增量落盘一遍;manifest 原子切换保证任意时刻目录都是一致状态。重写失败重试还有速率限制,不会刷出无数文件。

Hybrid · aof-use-rdb-preamble

混合持久化:RDB 打头阵,AOF 收增量

机制

aof-use-rdb-preamble(4.0 引入,5.0 起默认 yes):重写时 BASE 文件用 RDB 二进制格式写全量快照,之后的增量继续以 RESP 命令追加进 INCR 文件。文件头 = 高密度 RDB;尾巴 = 少量 AOF 文本。

效果:重写后的"账本"从 100 万条命令变成 1 个 RDB 快照 + 少量增量——AOF 体积接近 RDB、恢复速度接近 RDB、耐久性仍是 AOF 级。

恢复流程

启动时读 manifest → 先加载 BASE(识别到 RDB 魔数走 RDB 解析,日志 "Reading RDB preamble from AOF file")→ 再顺序重放各 INCR 的命令尾部。两段都完成才代表恢复成功;INCR 尾部截断可按 aof-load-truncated 容忍。

追问答案
为什么混合快?RDB 是紧凑二进制全量加载,比逐条解释执行百万条命令快一个量级;增量部分通常很小
为什么混合小?快照二进制密度远高于文本命令;老版本全 AOF 格式重写后依然要存"重建用的最短命令",混合后直接存状态本身
关掉它图什么?几乎不图——只有需要"纯文本 AOF 可人工审计/编辑"时才设 no(比如手工删掉误操作命令再恢复)
版本口径4.0 引入参数、5.0 起默认 yes;7.0 起混合形态自然演化为 Multi-Part 的 BASE(RDB)+INCR(AOF)
混合持久化一句话:重写时用 RDB 二进制写全量快照做文件头,之后增量继续用 AOF 命令追加。参数 4.0 引入、5.0 起默认 yes,现在生产基本都开着。收益是两边占:体积接近 RDB 因为存的是状态不是命令,恢复接近 RDB 因为加载二进制比重放命令快一个量级,耐久性还是 AOF 每秒落盘的水平。恢复流程两段:先认 RDB 魔数加载 BASE,再重放 INCR 尾巴。唯一关掉的理由是需要纯文本 AOF 做人工审计。

Operations Cheat Sheet

AOF 运维手册:损坏修复与参数速查

文件损坏的两档处理

尾部截断(宕机写一半):默认 aof-load-truncated yes 直接丢弃残缺尾命令照常启动;要严格就设 no。中部损坏(非法字节):启动报 Bad file format 拒绝启动——先备份,跑 redis-check-aof --fix(7.0+ 支持 Multi-Part,对 manifest 整组修);注意 fix 会把损坏点到末尾全部丢弃,可能大量丢数据。

备份(7.0 起是"整目录")

RDB:任意时刻拷贝 dump.rdb 都安全(rename 原子、生成后只读)。AOF:拷贝整个 appendonlydir;但重写进行中拷贝可能不一致——官方流程:CONFIG SET auto-aof-rewrite-percentage 0 暂停自动重写 → INFO 确认 aof_rewrite_in_progress=0 → 拷目录 → 恢复配置。

参数默认一句话
appendonlynoAOF 总开关(7.0 起打开即自动生成 appendonlydir 三件套)
appendfsynceverysec落盘策略(第 8 页)
no-appendfsync-on-rewriteno设 yes = 重写/BGSAVE 期间不 fsync(避免磁盘 IO 抖动,代价是该窗口丢数据风险升高)
aof-use-rdb-preambleyes混合持久化开关
aof-load-truncatedyes容忍尾部截断
auto-aof-rewrite-percentage / min-size100 / 64mb自动重写触发(翻倍 + 起步量)
aof-timestamp-enabledno在 AOF 中记录时间戳(用于按时间点恢复,有开销)
运维页记四件事。损坏分两档:尾部截断默认容忍直接丢尾巴启动,aof-load-truncated 管;中部损坏拒绝启动,用 redis-check-aof 先看后修,fix 会砍掉损坏点之后全部,可能大丢数据。备份:RDB 任意时刻可拷,AOF 7.0 起要拷整个目录且先停自动重写。参数重点两个:no-appendfsync-on-rewrite 默认 no,设 yes 是用安全换重写期间的延迟稳定;auto-aof-rewrite 两个默认值 100 和 64MB 连着背。

RDB vs AOF vs Hybrid

横向对比与生产选型

维度RDBAOF(everysec)混合(默认形态)
数据安全丢分钟级(save 点间隙)丢 1~2 秒丢 1~2 秒(同 AOF)
写性能影响几乎无(fork 之外零磁盘 IO)write 常态开销小;fsync 在后台;重写要 fork同 AOF;重写频率更低(体积小 → 增长 100% 更慢)
文件体积最小(二进制快照)大(文本命令 + 需重写压缩)接近 RDB
恢复速度慢(逐条重放)快(RDB 头 + 少量增量)
可读/可审计无(二进制)RESP 文本可读可编辑头不可读、尾可读
兼容性/风险点版本升级兼容性好重写开销、缓冲(<7.0)风险依赖 RDB 解析正确性;旧版本 Redis 不识别 RDB 头

官方选型逻辑(persistence 文档)

要 PostgreSQL 级安全 → 两个都开;能忍分钟级丢失 → RDB 单开即可;官方不鼓励 AOF 单开——定期 RDB 对备份、快速重启、防 AOF 引擎 bug 都是保险。

生产落地建议

缓存:RDB(甚至无持久化)+ 从库兜底;数据重要:AOF everysec + 混合 + RDB 双开,机器内存按 maxmemory×2 预留,关 THP;配合从库与哨兵实现"持久化 + 复制"双保险(replication deck)。

对比表六个维度按官方口径填:数据安全上 RDB 丢分钟级、AOF 丢一到两秒;性能上 RDB 几乎零影响、AOF 常态开销小但重写要 fork;体积与恢复速度混合都接近 RDB。选型背官方逻辑:要 Postgres 级安全就双开,能忍分钟级丢失就 RDB 单开,官方明确不鼓励 AOF 单开。生产建议连着 thread-model 那本 deck 的运维三件套一起说:混合 + 双开 + 内存预留 + 关 THP + 从库兜底,这是一套完整的答案。

Interview QA · 1/2

RDB 与机制 8 连问

1 · SAVE 和 BGSAVE 的区别?

阻塞 vs fork生产禁用 SAVE

SAVE 由主线程同步写 RDB,全程阻塞;BGSAVE fork 子进程写临时文件后 rename 原子替换,父进程只在 fork 瞬间阻塞(页表复制)。save 点触发、主从全量同步走的都是 BGSAVE。

2 · BGSAVE 期间还能处理写请求吗?一致性怎么保证?

COW时间点一致

能。fork 后父子共享物理页;父进程写某页时内核 COW 复制该页给父进程,子进程始终读到 fork 瞬间的旧数据——快照是 t0 时刻的一致视图。子进程越快写完,COW 放大越小。

3 · fork 会阻塞 Redis 吗?多久?

瞬时阻塞页表复制可达 1 秒级

会,阻塞时长 = 内核复制页表的时间,正比于数据集页数:10GB ≈ 21MB 页表。官方口径:数据集大 + CPU 弱时可停毫秒级甚至 1 秒。配套手段:关 THP(否则 COW 粒度 2MB,放大 512 倍)、控实例规模。

4 · RDB 的 save 配置怎么读?默认几个触发点?

3600/1 · 300/100 · 60/10000

save N M = N 秒内至少 M 次修改则触发 BGSAVE。8.x 默认三个:3600 秒 1 次、300 秒 100 次、60 秒 10000 次。另:SIGTERM 正常关闭(有 save 点)也会保存;save "" 全关;AOF 重写进行中 BGSAVE 排队。

5 · RDB 优缺点各说三条?

紧凑/恢复快/父进程零磁盘IO丢窗口/fork 代价

优:单文件紧凑适合备份灾备、父进程除 fork 不做磁盘 IO、大 dataset 恢复快且副本重启可部分重同步。缺:save 点间隙宕机丢分钟级数据、fork + COW 有瞬时阻塞与内存放大。

6 · COW 期间最坏内存会怎样?怎么防?

最坏翻倍OOM预留内存

BGSAVE 期间父进程写入热点的每个涉及页都可能被复制,最坏(全部页被写)内存逼近翻倍,可能触发 OOM killer。防:机器内存 ≥ maxmemory×2、监控 RSS、关 THP、低峰触发快照、控制单实例规模。

7 · kill -9 后数据丢多少?正常 shutdown 呢?

kill -9 无兜底SIGTERM 会保存

kill -9 走不到任何持久化钩子:丢到"上一次 save 点/AOF 最后一次 fsync"为止。SIGTERM(redis-cli shutdown)触发时若有 save 点会先 BGSAVE 再退出(save "" 关闭则不保存)。

8 · RDB 在主从复制里还有什么用?

全量同步载体部分重同步

两个用途:全量同步时主库 bgsave 生成 RDB 发给从库加载;RDB 文件里存有 replication id/offset,副本重启或 failover 后可借此做部分重同步(官方:RDB supports partial resynchronizations)。

RDB 这组 QA 的杀手锏是量级:fork 阻塞能算出 10GB 约 21MB 页表,官方说极端一秒;COW 最坏内存翻倍。第三题把 THP 带上是生产加分项。第七题的 kill -9 与 SIGTERM 区别是容易被忽略的边界:正常关闭有 save 点会先落盘。第八题的部分重同步是 RDB 在主从里的隐藏作用,和 replication deck 串起来答。

Interview QA · 2/2

AOF 与选型 8 连问

9 · AOF 为什么是"写后日志"?好处与代价?

先执行后记录不记坏命令

与 WAL 相反:命令执行成功才追加。好处:不阻塞写操作、日志里只有成功命令,重放安全;代价:执行到落盘之间存在丢失窗口——由 fsync 策略控制(write 每轮做,fsync 按 appendfsync)。

10 · everysec 为什么说"最多丢约 2 秒"?

后台 fsync推迟写 2s 上限

fsync 在 BIO_AOF_FSYNC 后台线程执行;上次 fsync 未完成时,主线程推迟本轮 write 并记下推迟起点(aof_flush_postponed_start),推迟满 2 秒才强制执行。因此 aof_buf 滞留命令最多约 2 秒——官方口径"丢 1 秒",机制上界 2 秒。

11 · AOF 重写是什么?为什么不能简单滚动删除旧日志?

最短命令集fork 快照式重建

重写 = fork 子进程按当前内存生成"重建数据集的最短命令序列",等效于用状态替换历史。不能滚动删旧文件:日志是整体有序的命令流,按行删无法保证重放语义;且压缩的核心是"按 key 合并",本质与快照同构——所以复用 fork/COW。

12 · 7.0 Multi-Part AOF 解决了什么?三件套怎么协作?

base+incr+manifestaof_rewrite_buf 移除

解决 <7.0 重写的三宗罪:内存缓冲翻倍、命令写两遍、结尾补写冻结。现在重写期父进程直接打开新 INCR 文件写盘,子进程生成新 BASE,完成后临时 manifest 原子 rename 生效,旧文件标 HISTORY 清理——任意时刻目录都是一致状态。

13 · 混合持久化是什么?默认开吗?

RDB 头 + AOF 尾5.0 默认 yes

aof-use-rdb-preamble:重写时 BASE 用 RDB 二进制全量、增量继续 AOF 命令追加。4.0 引入、5.0 起默认 yes。体积与恢复速度接近 RDB、耐久性保持 AOF;7.0 后自然落位为 Multi-Part 的 BASE(RDB)+INCR(AOF)。

14 · RDB 和 AOF 都开,重启加载谁?

AOF 优先最完整

AOF 优先:官方明确"AOF 重建的数据集更完整"。流程:识别 manifest → 加载 RDB 头(若混合)→ 重放 INCR 尾。RDB 只在未开 AOF 时用于恢复。

15 · AOF 文件坏了怎么办?truncate 和 corrupt 的区别?

aof-load-truncatedredis-check-aof --fix

尾部截断(写一半):默认 aof-load-truncated=yes,丢弃残尾照常启动。中部损坏(Bad file format):拒绝启动,先备份再 redis-check-aof --fix(7.0+ 支持整组 manifest);fix 从损坏点截到末尾,可能大丢数据,官方建议先手工分析偏移。

16 · 生产上 RDB/AOF 怎么组合?哪些参数必须过一遍?

双开 + 混合内存×2 + 关 THP

数据重要:appendonly yes + appendfsync everysec + aof-use-rdb-preamble yes + RDB save 保留(官方不鼓励 AOF 单开)。同时:机器内存 ≥ maxmemory×2(COW/缓冲)、关 THP、no-appendfsync-on-rewrite 默认 no、监控 aof_last_bgrewrite_status 与 AOF 增长速率。纯缓存:RDB 甚至不持久化。

AOF 这组的深度点:第十题的两秒机制必须讲到 aof_flush_postponed_start 和强制写,这是真读过源码的信号。第十一题"为什么不能滚动删日志"是反直觉好题:命令流的删除会破坏重放语义,压缩本质是按 key 合并,和快照同构。第十二题三件套答全再报 aof_rewrite_buf 已移除。第十五题损坏分两档处理是运维高频事故。最后一题把参数清单串成完整选型答案收尾。

Related · Cross-links

相关知识点

Redis 领域 · 同批 deck

replication-cluster · 主从与集群(全量同步 = BGSAVE 产物;部分重同步依赖 RDB 元数据)
thread-model · 单线程模型(aof_buf / beforeSleep flush / BIO_AOF_FSYNC 的线程视角)
memory-policy · 过期删除与内存淘汰(过期键与 RDB/AOF 的交互、COW 与 maxmemory 的联动)
data-structures · 对象系统与数据结构(编码紧凑度 → RDB 体积与恢复速度)

跨领域 · 答题串联

MySQL MVCC:AOF 写后日志 ↔ MySQL redo log 先写日志,两种 WAL 取舍对照
Kafka Internals:AOF 追加日志 + 重写压缩 ↔ Kafka 分段日志 + 日志压实(log compaction)
OS · 进程线程协程:fork + 写时复制机制详解——bgsave 卡顿与内存翻倍的根源
通用线索:快照 vs 日志顺序 IO后台线程做重活三组思想反复出现

收尾串联:主从同步用的 RDB 就是本 deck 的 BGSAVE 产物,部分重同步依赖 RDB 里的复制元数据;thread-model 里讲过的 beforeSleep 和 bio 线程正是 AOF 落盘的执行者;过期键怎么进 RDB/AOF 在 memory-policy 展开。跨领域最有意思的对照是 Kafka:AOF 加重写和 Kafka 分段日志加压实是同一套"日志膨胀治理"思想,MySQL redo log 的先写日志则和 AOF 写后日志形成取舍对照。

References

参考来源

本 deck 全部结论可溯源至下列一手材料——数字与版本结论以原文为准。

redis.io/docs/latest/operate/oss_and_stack/management/persistence/RDB/AOF 优缺点原文、fork 流程、appendfsync 三策略、<7.0 与 ≥7.0 重写流程、损坏修复与备份流程
redis.io/blog(8.0-M03 / 8.0 GA)· 7.0 00-RELEASENOTESMulti-Part AOF 设计(manifest 原子替换、rewrite limiting);7.0 起空集合不落日志等行为变化
github.com/redis/redis · 8.2 redis.conf默认值核对:save 3600 1 / 300 100 / 60 10000、appendfsync everysec、aof-use-rdb-preamble yes、aof-load-truncated yes、auto-aof-rewrite 100/64mb、no-appendfsync-on-rewrite no
github.com/redis/redis · src/aof.cflushAppendOnlyFile 的 write/fsync 分离、aof_flush_postponed_start 2 秒强制逻辑、feedAppendOnlyFile
github.com/redis/redis · src/rdb.c / src/bio.hrdbSaveBackground、temp-<pid>.rdb 与 rename(2);BIO_AOF_FSYNC / BIO_CLOSE_FILE / BIO_LAZY_FREE 三线程
redis.io/docs latency(THP)· latency-monitor透明大页禁用建议(COW 粒度 2MB 放大)
redis.io/docs eviction复制/AOF 缓冲不计入 maxmemory(mem_not_counted_for_evict)——持久化实例内存预留依据
所有默认值都来自 8.2 redis.conf 的实测核对,fork 卡顿与内存翻倍的根源在 OS 那本 deck 的写时复制条目里,复习时把两处连起来看。