城里有个叫阿雷的账房先生,记性极好,全城的账目都装在他脑子里。老板查账,他张口就来,速度无人能及。可这本事有个致命隐患:每次他打盹摔下凳子(宕机),或者遇上停电(重启),脑子里那些账目就会乱成一团,甚至全部清零。老板吓坏了,勒令他必须找到“防失忆”的办法。阿雷冥思苦想,掏出两样家当:一台照相机和一册流水账本。照相机每隔一段时间就把全部账目拍成一张全景照片——这就是 Redis 的 RDB 快照;流水账本则把每一笔交易当场记上一行,只许往后添、不许改旧账——这就是 AOF 日志。照片恢复起来一翻就完事,但会漏掉拍照之后新发生的账目;流水账几乎一条不落,可本子越来越厚,恢复时要从头念到尾。后来阿雷干脆把两者合体:先拍一张照片垫底,后面再补新账,既快又不丢。Redis 的持久化,讲的就是这套“防失忆”的学问。读完这篇,你就能给任何业务挑出最合适的记账方式。

岗位关联

学会 RDB 与 AOF 的选型,是掌握 Redis 高可用与数据安全的必修课。对运维与后端工程师而言,这决定了宕机时少丢多少数据、恢复时少花多少时间,直接关系事故级别与 SLA 达标率。

目录
  1. 可视化演示
  2. 持久化为何重要
  3. RDB快照原理
  4. AOF日志原理
  5. 性能与数据安全对比
  6. 混合持久化解析
  7. 配置参数实践
  8. 选型场景与建议
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示RDB快照bgsave fork机制与COW原理、AOF追加日志与自动重写策略、RDB与AOF性能恢复对比。切换模式理解Redis持久化选型核心。

本节配有交互动效(进入视口后自动加载,约 3KB)。

RDB快照与bgsave fork机制 bgsave通过fork子进程异步创建快照,利用COW写时复制保证主进程不受阻塞 RDB文件紧凑体积小,恢复速度快,但可能丢失最后一次快照后的数据 bgsave → fork → COW → dump.rdb RDB适合备份和灾难恢复

持久化为何重要

阿雷最风光的时候,老板夸他“活账本”。可老板不知道,这个“活账本”最怕的就是停电——记忆存在脑子里,电一断,一切归零。Redis 就像是这个“活账本”,它的数据全部存放在内存(RAM)中,内存速度快到百万级读写每秒,但代价是“断电即失”。如果机器崩溃、机房停电、进程被杀,还没来得及落盘的数据就会全部消失,这在生产环境里往往是事故级别的问题。

持久化的本质,就是把“脑子里的账目”定期誊写到“纸面上”——也就是磁盘。这样无论机器怎么重启,Redis 都能把纸面上的账目重新念进脑子里,恢复营业。Redis 为此提供了两种“防失忆”工具:RDB(Redis DataBase)快照,相当于每隔一段时间给全部账目拍一张全景照片;AOF(Append Only File)日志,相当于把每一笔交易当场记进一册只许追加的流水账本。

这两种工具的取舍,归根结底是在衡量“丢了多少钱”和“多久能恢复营业”这两个指标。任何持久化方案都无法同时做到绝对不丢数据和瞬间恢复,我们需要的是一把尺子来衡量优劣。

所以全文的主角不是两个神秘的算法,而是两种“记账习惯”:RDB 习惯隔一阵子拍一次照,AOF 习惯每笔都落笔。接下来我们分别看看,照片是怎么拍的,流水账是怎么记的。

L丢失=R写入×T落盘间隔\displaystyle L_{\text{丢失}} = R_{\text{写入}} \times T_{\text{落盘间隔}}
丢失的数据量 = 写入速度 × 两次落盘之间的时间间隔。这是贯穿全篇的总公式,RDB 和 AOF 的差异,本质就是这个“落盘间隔”不同。
  1. 丢失量 L宕机时未能写入磁盘的数据量,单位与写入速率一致,可以是“条数”或“字节数”。
    L丢失L_{\text{丢失}}
  2. 写入速率 R业务每秒产生的写命令数量,代表账本上新发生的变动速度,业务越繁忙这个值越大。
    R写入=新账条数秒R_{\text{写入}} = \frac{\text{新账条数}}{\text{秒}}
  3. 落盘间隔 T两次真正把内存数据写进磁盘之间的时间差。间隔越大,空窗期越长,新账目就越危险。
    T落盘间隔T_{\text{落盘间隔}}
  4. 乘积含义把“每秒新账”乘以“空窗秒数”,得到的就是这段空窗期内累积的、没有被保护起来的数据总量。理解它,就握住了持久化选型的总钥匙。

RDB快照原理

阿雷的照相机有个独特设计:拍照时并不需要他停下手中的活。他请来一位“分身”(子进程),让分身端着相机站在一边,对着账本咔嚓一下,而他自己继续记账、收钱、拨算盘。这位分身拍到的,必须是“咔嚓那一瞬间”账本的样子——哪怕主人在拍照的同一秒又改了几页账,也不能影响照片内容。

这个“分身拍照”的机制在 Redis 里叫 bgsave(后台保存)。Redis 调用操作系统的 fork 系统调用,生成一个子进程;子进程继承父进程此刻的全部内存数据,把数据序列化后写入 RDB 文件;父进程则继续服务所有读写请求,全程不阻塞业务。生成的 RDB 文件是一个紧凑的二进制文件,恢复时直接加载进内存即可。

这里的关键技术叫“写时复制”(Copy On Write,COW)。fork 之后,父子进程其实共享同一份物理内存页,并没有立刻复制整个内存——那会既慢又吃内存。只有当父进程要修改某个内存页时,操作系统才会先把这一页复制一份给子进程用,父进程改自己的那份。这样一来,子进程眼里永远是 fork 时刻的数据视图,而父进程的写操作也几乎不受影响。触发 bgsave 的时机包括:满足 save 配置的变更条件、手动执行 BGSAVE、正常关闭 Redis、以及主从复制时的全量同步。

照片方案的优点很突出:文件小、加载快,恢复时“翻开照片一对照”就完成了,几 GB 的数据也只需秒级到分钟级。缺点也同样藏在照片本身——它只记录按快门那一刻的账目,一旦在两次拍照之间发生宕机,这期间的每一笔新账都找不回来。换句话说,RDB 的丢失窗口不是一个点,而是整个快照间隔。

save m n:  Δt≤m 秒 且 Δc≥n⇒触发 bgsave\displaystyle \text{save } m\ n:\ \ \Delta t \le m \ \text{秒}\ \text{且}\ \Delta c \ge n \Rightarrow \text{触发 bgsave}
这是 Redis 配置文件中 save m n 的语义:在 m 秒内如果发生了至少 n 次写操作,就触发一次后台快照。两个条件必须同时满足,避免空拍浪费资源。
  1. 时间窗口 Δt自上次快照以来经过的时间,单位是秒,对应配置里的 m。例如 save 900 1 中 m=900。
    Δt≤900 秒\Delta t \le 900\ \text{秒}
  2. 变更次数 Δc窗口内累计发生的写命令条数,对应配置里的 n。例如 15 分钟内至少有 1 次写。
    Δc≥1\Delta c \ge 1
  3. 双条件触发且 表示两个条件同时成立才拍照。如果 15 分钟内一单生意都没有(0 次写),就不必白拍一张照片。
    Δt≤m∧Δc≥n\Delta t \le m \land \Delta c \ge n

AOF日志原理

阿雷后来觉得光拍照片不踏实,于是又添了一册流水账本。规矩很简单:每一笔交易发生,他立刻在本子上记一行,比如“10 点 03 分,张三存入 100 两”。这册账本有个铁律——只许往末尾追加新行,绝不许回头改动任何旧行。这样哪怕摔了脑子,只要把流水账从第一行念到最后一行的“重放”一遍,账目就原封不动地回来了。

这就是 AOF(Append Only File)的核心思想。Redis 把每一条写命令(SET、LPUSH、DEL 等)按照自己的通信协议格式,追加写入到一个日志文件的末尾。追加写入是顺序 IO,磁盘对顺序写的速度非常友好,所以单条命令的日志开销比随机写小得多。恢复时,Redis 启动后从头到尾读取这个文件,把每行命令重新执行一遍,数据就在内存中重建了。

AOF 有一个精妙的细节:命令先写入内存中的缓冲区,并不会立刻落到磁盘。真正决定“什么时候掉到磁盘上”的是 appendfsync 配置,它有三个档位:always 表示每执行一条写命令就同步一次磁盘,最安全但最慢;everysec 表示每秒把缓冲区内容同步一次,这是默认值,性能与安全兼顾;no 表示完全交给操作系统决定何时刷盘,性能最好但丢失窗口不可控。此外,日志无限追加会越来越臃肿,所以 Redis 提供 bgrewriteaof 重写机制:把当前内存数据重新整理成最小命令集,生成一份精简的新日志替换旧日志。

流水账方案几乎滴水不漏,代价是“念账”太慢:恢复时要把全部命令从第一条重放到最后一条,日志越大恢复越久。而且文本文件的体积通常是二进制快照的数倍。所以 AOF 需要定期“誊写乱账”——也就是重写机制——来控制体积。这就引出了下一个问题:照片和流水账,到底哪个更划算?

LAOF=R写入×Tfsync,Tfsync≤1 秒\displaystyle L_{AOF} = R_{\text{写入}} \times T_{\text{fsync}} ,\quad T_{\text{fsync}} \le 1\ \text{秒}
AOF 的丢失量公式和总公式形式一样,但关键在于:everysec 策略把落盘间隔压缩到了 1 秒以内,所以最多丢 1 秒的写入。这是 RDB 望尘莫及的精度。
  1. fsync 间隔 Teverysec 意味着每秒强制刷一次盘,因此空窗期不超过 1 秒。
    Tfsync≤1 秒T_{\text{fsync}} \le 1\ \text{秒}
  2. 写入速率 R业务每秒产生的写命令数量,与 RDB 公式中的含义完全相同。
    R写入R_{\text{写入}}
  3. 上限估算把速率和 1 秒相乘,就是最坏情况下的丢失量。例如每秒写入 1 万条命令,最多丢 1 万条,而不是几分钟的数据。
    LAOF≤R写入×1L_{AOF} \le R_{\text{写入}} \times 1

性能与数据安全对比

老板给阿雷出了道考题:假设现在账房起火,你只能带一样备份逃命,带照片还是带流水账?阿雷犯了难。带照片,逃出去之后一翻照片就能立刻开张,但最近几天的新账全没了;带流水账,账目分毫不差,可要重新念几百页的旧账,念完才能重新营业。横竖都是损失,只是损失的东西不同。

这正是 RDB 与 AOF 的本质差异。恢复速度上,RDB 直接加载二进制快照,一步到位;AOF 需要逐条重放命令,恢复时间是 RDB 的数倍到数十倍。数据安全上,RDB 的丢失窗口等于整个快照间隔(分钟级甚至小时级),而 AOF 在 everysec 下最多丢 1 秒。写入性能上,RDB 平时几乎零开销,只有拍照瞬间有一次集中写盘;AOF 每一条命令都要追加写日志,always 模式下更是每条都 fsync,延迟和 IO 压力显著上升。文件体积上,RDB 是压缩过的二进制格式,紧凑;AOF 是文本协议,通常要膨胀数倍。

既然两边各有短板,阿雷灵机一动:能不能把照片和流水账合在一本里用?这正是下一节要讲的混合持久化——先用照片垫底,再在照片后面补记新账,恢复时先翻照片秒级还原,再快速补念几行增量日志,两头的好处都占到。

WRDB=T快照间隔,WAOF≤1 秒,T恢复RDB≪T恢复AOF\displaystyle W_{RDB} = T_{\text{快照间隔}},\quad W_{AOF} \le 1\ \text{秒},\quad T_{\text{恢复}}^{RDB} \ll T_{\text{恢复}}^{AOF}
用两个指针总结:RDB 的丢失窗口宽但恢复指针短,AOF 的丢失窗口窄但恢复指针长。选型就是在“丢得多、起得快”和“丢得少、起得慢”之间找平衡点。
  1. RDB 丢失窗口等于两次快照之间的时间跨度,比如配置 save 900 1 时窗口可达 15 分钟。
    WRDB=T快照间隔W_{RDB} = T_{\text{快照间隔}}
  2. AOF 丢失窗口在默认每秒刷盘策略下不超过 1 秒,这是两者最悬殊的差距。
    WAOF≤1 秒W_{AOF} \le 1\ \text{秒}
  3. 恢复时间对比远小于符号表示 RDB 与 AOF 的恢复耗时不在一个量级,差几十倍很正常。
    T恢复RDB≪T恢复AOFT_{\text{恢复}}^{RDB} \ll T_{\text{恢复}}^{AOF}

混合持久化解析

阿雷最终想出的妙招是:每天清晨开门前,先给账本拍一张全家福,把照片贴在流水账本的第一页;之后的一整天,新交易仍然逐行记在照片后面的空白页上。万一中途出事,恢复流程变成两步:先看照片,瞬间还原到清晨的状态;再念照片后面那几页新账,补上今天的变动。既不用从头念几百页旧账,又一条账都不丢。

Redis 4.0 起提供了这个“合体方案”,开关是配置项 aof-use-rdb-preamble,默认开启。它的工作机制是:每当执行 AOF 重写(bgrewriteaof)时,Redis 不再生成纯文本日志,而是先在文件头部写一段当前数据的 RDB 快照,再在后面追加重写之后的新命令日志。这样生成的 AOF 文件,格式上仍然是一个 .aof 文件,内部却包含“RDB 前缀 + 增量 AOF 后缀”两部分。

恢复时,Redis 会先识别文件头部的 RDB 段,直接加载这些二进制数据(速度接近纯 RDB),然后从 RDB 段的末尾继续重放增量日志。因为增量日志只包含“拍照之后”的命令,量通常很小,重放也就一瞬间。对比来看,混合文件比纯 AOF 小得多——纯 AOF 要记录全部历史命令,而混合文件只需一张快照加少量增量。这也让混合模式成为 4.0 之后生产环境的默认推荐。

混合持久化就像“照片打底 + 流水补账”,在恢复速度与数据安全之间找到了黄金分割点。不过要让它跑得稳,还得学会配置参数。下一节我们看看,阿雷是怎么给账房立规矩的。

V混合=VRDB+V增量≪V纯AOF\displaystyle V_{\text{混合}} = V_{\text{RDB}} + V_{\text{增量}} \ll V_{\text{纯AOF}}
混合方案的体积公式:一张照片的体量 + 拍照后新增的少量日志,远小于纯 AOF 的全部历史命令。这就是“又快又全”的代价经济学。
  1. 打底体积 V_RDBRDB 段是一份压缩的二进制快照,体量等于任一时刻全部数据压缩后的大小。
    VRDBV_{\text{RDB}}
  2. 增量体积 V_增量只记录重写之后发生的新命令,通常只是快照体积的极小一部分。
    V增量≈R×Δt重写后V_{\text{增量}} \approx R \times \Delta t_{\text{重写后}}
  3. 远小于纯 AOF纯 AOF 记录的是全部历史命令,可能积累几天甚至几周的操作,自然膨胀数倍。
    V混合≪V纯AOFV_{\text{混合}} \ll V_{\text{纯AOF}}

配置参数实践

光有工具还不够,阿雷给账房定下了三条规定:第一,账目变动达到什么规模才拍照;第二,流水账多久往抽屉里锁一次(刷盘);第三,流水账厚到什么程度必须重新誊写一遍。这三条规定在 Redis 里就是一组配置参数,把它们设对,持久化就成功了一大半。

第一条规矩对应 RDB 的触发策略:save 900 1、save 300 10、save 60 10000 可以同时配置多条,满足任意一条就会触发 bgsave。第二条规矩对应 AOF 的刷盘策略:appendfsync everysec 是默认推荐,always 给最较真的场景,no 则适合对丢失不敏感的大批量写入场景。第三条规矩对应 AOF 自动重写的两个阈值:auto-aof-rewrite-percentage 100 表示当前日志比基础日志大一倍时触发重写;auto-aof-rewrite-min-size 64mb 则防止日志还很小的时候就频繁重写。

一个典型的配置文件片段长这样:先开启 appendonly yes,再设置 appendfsync everysec,用 save 定义快照节奏,最后配上自动重写阈值。理解这些参数背后的公式比死记默认值更重要——重写触发条件本质上是一个比值判断。

配置不是玄学,而是把前面所有原理翻译成数字。先想清楚业务能接受多少丢失、希望多快恢复,再回头设置这些参数,才不会被一堆默认值牵着鼻子走。最后一节,我们把所有的公式和工具放回真实业务场景里,给出最终的选型答案。

S当前S基础≥p百分比100⇒触发自动重写\displaystyle \frac{S_{\text{当前}}}{S_{\text{基础}}} \ge \frac{p_{\text{百分比}}}{100} \Rightarrow \text{触发自动重写}
当 AOF 当前文件大小达到基础大小的 p% 时(默认 p=100,即翻倍),Redis 自动执行 bgrewriteaof,把臃肿的日志压缩成精简版本。
  1. 当前大小 S_当前此刻 AOF 文件的体积,随着新命令追加而不断增长。
    S当前S_{\text{当前}}
  2. 基础大小 S_基础上次重写完成后 AOF 文件的体积,是衡量的基准线。
    S基础S_{\text{基础}}
  3. 百分比阈值 p默认 100,表示当前体积达到基准的两倍就触发重写;配合最小体积 64MB,防止过早重写。
    S当前S基础≥100100\frac{S_{\text{当前}}}{S_{\text{基础}}} \ge \frac{100}{100}

选型场景与建议

面馆和银行是两种截然不同的“账房”。面馆门口的排队队伍(缓存)丢了,大不了让客人重新排队;可银行的金库流水(交易记录)要是少了一笔,那是要出大事的。阿雷给两类老板的建议完全不同:面馆老板用照片方案就够——数据丢了可以重建,重要的是翻照片快,队伍能立刻排起来;银行老板必须用流水账——哪怕恢复慢一点,账目也必须分毫不差。

在技术语境里,这个判断标准叫做“数据可重建性”。排行榜、热点新闻、推荐结果这类缓存数据,底层有数据库或离线任务兜底,丢了随时能重建,优先选 RDB,享受快速的恢复时间。购物车、订单状态、支付凭证、用户会话这类强一致数据,一旦丢失就是真金白银的损失,必须选 AOF,甚至 appendfsync always。而绝大多数生产系统里两类数据并存,这时混合持久化就是最稳的均衡解。

生产环境的最佳实践可以浓缩成几条铁律:开混合持久化(RDB 打底 + AOF 增量)作为默认;定期把 RDB 文件备份到异地,防止磁盘故障把快照和日志一起带走;监控快照触发频率与 AOF 重写是否正常,避免日志无限膨胀;大实例执行 bgsave 时留意 fork 的毫秒级停顿,错峰执行手动备份;最后,持久化永远替代不了主从复制,两者搭配才叫高可用。

所以,“RDB 和 AOF 怎么选”没有标准答案,只有针对业务的答案。可重建的数据用照片,分毫必争的数据用流水账,拿不准就上混合方案——这既是阿雷的生意经,也是 Redis 持久化最实用的决策树。

方案代价=α×T恢复+β×L丢失,选择代价最小者\displaystyle \text{方案代价} = \alpha \times T_{\text{恢复}} + \beta \times L_{\text{丢失}},\quad \text{选择代价最小者}
选型公式:给“恢复时间”和“丢失数据量”各设一个权重系数 α 和 β,乘积之和就是方案的代价。缓存场景 α 大(更看重恢复快),交易场景 β 大(更看重不丢数据),算出代价最低的方案就是答案。
  1. 恢复权重 α业务对停机时间的敏感度。每多停一秒就少赚一秒的钱时,α 就很大,比如在线支付网关。
    α×T恢复\alpha \times T_{\text{恢复}}
  2. 丢失权重 β业务对数据损失的敏感度。少一笔订单都无法接受时,β 就很大,比如金融核心交易。
    β×L丢失\beta \times L_{\text{丢失}}
  3. 代价最小化把所有候选方案(纯 RDB / 纯 AOF / 混合)的代价分别算出,取最小值对应的方案。
    arg⁡min⁡{代价RDB,代价AOF,代价混合}\arg\min \{ \text{代价}_{RDB}, \text{代价}_{AOF}, \text{代价}_{混合} \}

AI 应用场景

电商订单状态与购物车容灾

阶段:数据基础设施容灾设计

解决的问题:传统方案只开 RDB 时,大促峰值一旦宕机,会丢失几分钟内的购物车与订单状态,用户侧表现为“购物车空了、订单状态回退”。改用 AOF everysec 加混合持久化后,重启最多丢 1 秒的数据,配合主从切换,用户几乎无感知。

具体案例:某电商平台大促期间,用户加购与下单状态全部存储在 Redis。一次缓存节点硬件故障,运维团队依靠混合持久化的快速恢复能力,在 1 分钟内完成故障转移与数据恢复,订单数据零丢失,客服投诉量当天为零。

热点排行榜与推荐缓存重建

阶段:模型服务缓存管理

解决的问题:排行榜和推荐结果本质是可重建数据,底层由离线任务重新计算,不需要逐条操作日志。用 RDB 恢复最快,发布或扩容时能显著缩短实例空窗期,降低对在线服务的影响。

具体案例:某内容平台的推荐服务把 Top N 结果写入 Redis 缓存,缓存重建由每小时的离线任务完成。采用 RDB 后,一次版本发布重启,3 秒内加载完 2GB 快照文件,服务立刻恢复,推荐接口可用性保持在 99.99%。

在线推理特征缓存与模型版本配置

阶段:在线推理数据缓存

解决的问题:AI 特征服务需要重启后秒级恢复缓存,同时模型版本配置这类数据又要求强一致,不能丢更新。单一方案无法两全,混合持久化的“RDB 打底 + 增量补全”恰好同时满足两个硬性要求。

具体案例:某风控系统的特征缓存达 8GB,模型每周发布新版本。开启混合持久化后,一次例行重启 2 秒完成 RDB 前缀加载,随后重放最近 1 秒的增量日志,特征完整、模型命中率不降,发布流程从 10 分钟缩短到 1 分钟。

Python 演示

python54 行
# -*- coding: utf-8 -*-# MiniRedis: 用"照片"和"流水账"演示 RDB 与 AOF 的恢复差异import copy class MiniRedis:    def __init__(self):        self.data = {}        self.snapshot = None        self.aof_log = []        self.snapshot_log_len = 0     def set(self, key, val):        self.data[key] = val        self.aof_log.append('SET %s %s' % (key, val))     def rdb_save(self):        # 拍快照: 把当前数据完整拷贝一份, 并记下日志水位        self.snapshot = copy.deepcopy(self.data)        self.snapshot_log_len = len(self.aof_log)     def recover(self, use_rdb, use_aof):        # 选择用哪本"备份"来恢复: 照片(rdb) 和/或 流水账(aof)        if use_rdb:            self.data = copy.deepcopy(self.snapshot)            start = self.snapshot_log_len        else:            self.data = {}            start = 0        if use_aof:            for line in self.aof_log[start:]:                _, key, val = line.split()                self.data[key] = val def main():    db = MiniRedis()    db.set('balance', '100')    db.rdb_save()          # 打烊前拍了一张全家福快照    db.set('balance', '80')    db.set('vip', 'alice') # 快照之后又来了两笔新账     rdb_only = copy.deepcopy(db)    rdb_only.recover(use_rdb=True, use_aof=False)    aof_only = copy.deepcopy(db)    aof_only.recover(use_rdb=False, use_aof=True)    hybrid = copy.deepcopy(db)    hybrid.recover(use_rdb=True, use_aof=True)     print('RDB photo :', sorted(rdb_only.data.items()))    print('AOF log   :', sorted(aof_only.data.items()))    print('hybrid    :', sorted(hybrid.data.items()))    print('aof lines :', len(db.aof_log)) if __name__ == '__main__':    main()
目的:用 40 行代码模拟一个迷你 Redis,分别用“快照副本”和“命令日志”两种方式恢复数据,直观对比 RDB、AOF、混合方案在宕机后的恢复结果差异。
  1. self.snapshot = copy.deepcopy(self.data)拍快照的本质就是完整拷贝一份当前数据,对应 RDB 子进程生成二进制文件。
  2. self.snapshot_log_len = len(self.aof_log)记下“拍照时流水账写到第几行”,相当于 RDB 快照的时刻水位,混合恢复时从这里继续。
  3. if use_rdb:选择用照片恢复:直接把快照拷回来,速度快但只还原到拍照那一刻。
  4. start = self.snapshot_log_len混合恢复的关键:只重放拍照之后的新日志,不用从头念整本账。
  5. for line in self.aof_log[start:]:重放流水账:从水位之后逐条执行命令,模拟 AOF 的恢复过程。
  6. _, key, val = line.split()解析日志行,把 SET key val 还原成内存中的键值对。

常见误区

误区我一直以为 bgsave 就是 fork 一个子进程,然后把整个内存数据复制一份给子进程写文件,所以保存期间内存占用会翻倍,8GB 的实例要 16GB 内存才敢开 RDB。

正解fork 之后父子进程共享同一份物理内存页,并不复制数据。只有父进程修改了某个内存页,操作系统才把那一页复制一份给子进程,这就是写时复制(COW)。10GB 实例做 bgsave,内存可能只增加几十 MB 到几百 MB,远不是翻倍。真正要注意的是 fork 本身需要复制页表,大实例会有毫秒级短暂停顿。

为何常见:把“快照”联想成“拍照复制一份”,自然认为内存要翻倍,忽略了操作系统层面的共享页与写时复制机制。

误区AOF 不是叫“追加文件”吗?那肯定每条命令一来就写到磁盘文件里了,所以 AOF 模式应该天然最安全、最慢。

正解写命令先进入 Redis 的内存缓冲区,真正落到磁盘的时机由 appendfsync 决定。everysec 模式下缓冲区最多攒 1 秒才刷一次盘;always 才是一条命令一次 fsync。而且即使进程把数据写给了操作系统,断电时操作系统页缓存里的数据同样可能丢失,所以没有绝对零丢失。

为何常见:看到“追加写入”就以为和磁盘同步是一回事,混淆了“写入系统缓冲区”和“刷到磁盘”两个阶段。

误区AOF 是文本日志,把每条命令都记下来,RDB 是压缩二进制,那 AOF 肯定永远比 RDB 大好几倍,选 RDB 一定更省磁盘。

正解没重写过的 AOF 确实容易膨胀数倍,但 AOF 有自动重写机制,重写后会把全部历史命令压缩成“当前数据的最小命令集”,体积可以变得很小。更优的是混合持久化,RDB 打底加增量日志,通常比纯 AOF 小、比纯 RDB 也大不了多少。比大小之前,先确认日志是否重写过。

为何常见:拿“运行了很久没重写的 AOF”和“刚生成的 RDB”做对比,忽视了重写机制这个关键变量。

误区我把 RDB 和 AOF 都打开了,那重启时 Redis 应该先加载 RDB 快照,再读 AOF 日志补充,两边数据合并,恢复得最全。

正解默认配置下,只要 AOF 开启,Redis 重启时只加载 AOF 文件,不会再去读 RDB。在 4.0 之后,混合持久化的 AOF 文件本身就包含 RDB 前缀,所以加载 AOF 既快又全,并不需要再叠加一个独立 RDB 文件。

为何常见:把“两种持久化可以同时开启”理解成“恢复时两个文件都要用”,忽略了 Redis 明确的优先级规则。

面试题

Q1. 关于 Redis 的 RDB 持久化,下列说法正确的是?

单选题 · easy

  • A.由主进程直接遍历内存生成快照文件
  • B.通过 fork 子进程生成快照文件← 正确答案
  • C.快照文件必须使用 AOF 格式保存
  • D.RDB 持久化完全不会阻塞任何服务

答案:B。RDB 通过 fork 出一个子进程,由子进程负责把内存数据写成快照文件,父进程继续服务,这就是 bgsave。A 错在主进程不直接做;C 错在 RDB 和 AOF 是两种独立格式;D 错在 fork 瞬间父进程仍有毫秒级停顿。好比照片是请“分身”拍的,主人不用停下手里的活,但请分身本身也要花一瞬。

Q2. Redis AOF 采用默认的 appendfsync everysec 策略时,最多可能丢失多久的数据?

单选题 · easy

  • A.最多丢失约 1 秒内的数据← 正确答案
  • B.最多丢失约 10 秒内的数据
  • C.最多丢失约 1 分钟内的数据
  • D.任何情况下都不丢失数据

答案:A。everysec 表示缓冲区每秒刷一次磁盘,因此宕机时最多丢最后一次刷盘后的数据,即约 1 秒。这相当于流水账本每秒钟往抽屉里锁一次,最多漏掉锁上之前那一瞬间还没誊写的一两行。always 才接近“不丢”,但性能开销大。

Q3. 关于 Redis 的 fork 与写时复制(COW)机制,说法正确的是?

单选题 · mid

  • A.fork 后父子共享内存页,修改时才复制该页← 正确答案
  • B.fork 会立刻复制全部内存导致内存翻倍
  • C.fork 过程中 Redis 完全停止直到完成
  • D.子进程负责接收新的写请求

答案:A。fork 后父子进程共享物理内存页,父进程首次修改某一页时才复制该页给子进程,这就是“写时复制”,所以内存不会翻倍,父进程也不停止服务。拍照时主人继续记账,只有被改动的账页才复印一份给拿相机的分身。子进程不接收请求,只负责写快照。

Q4. 下列哪些情况会触发 Redis 生成 RDB 快照?

多选题 · mid

  • A.满足 save 配置的变更次数和时间条件← 正确答案
  • B.手动执行 BGSAVE 命令← 正确答案
  • C.主从复制时全量同步需要生成快照← 正确答案
  • D.手动执行 BGREWRITEAOF 命令

答案:ABC。满足 save m n 条件、手动 BGSAVE、主从全量同步都会触发 RDB 快照;正常关闭时若配置允许也会保存。而 BGREWRITEAOF 触发的是 AOF 日志重写,与 RDB 快照无关。好比“拍照”和“誊写流水账”是两件不同的事,别把工具拿混了。

Q5. Redis 4.0 混合持久化开启后,AOF 文件内部的结构是什么样的?

单选题 · mid

  • A.开头一段 RDB 快照再加增量命令日志← 正确答案
  • B.从头到尾都是纯文本的写命令日志
  • C.由 RDB 与 AOF 两个独立文件组成
  • D.快照数据只保存在内存中不落盘

答案:A。混合持久化下,执行 AOF 重写时会在文件头部写入当前数据的 RDB 快照,后面续接重写之后的增量命令日志。恢复时先直接加载 RDB 段,再重放少量增量日志。就像照片贴在流水账本第一页,后面才是新账,而不是分成两个本子。

Q6. AOF 自动重写(bgrewriteaof) 的作用包括哪些?

多选题 · hard

  • A.压缩日志体积去除中间重复命令← 正确答案
  • B.加快重启时数据恢复的速度← 正确答案
  • C.减少日志对磁盘空间的占用← 正确答案
  • D.提高单条写命令的执行速度

答案:ABC。重写把整本历史流水账精简成“当前状态最小命令集”,体积变小、磁盘占用减少,恢复时重放的命令条数也变少,速度自然提升。但重写并不改变单条命令的执行路径,写命令的性能不受它影响。好比把记了一年的旧账誊成一张余额表,省纸又省念。

Q7. 与纯 RDB 相比,纯 AOF 持久化的主要劣势是什么?

单选题 · mid

  • A.恢复速度慢且文件体积通常更大← 正确答案
  • B.数据丢失量比 RDB 更大
  • C.完全无法保证任何数据安全
  • D.与主从复制机制不兼容

答案:A。AOF 恢复要逐条重放命令,速度比直接加载二进制快照慢得多;文本格式的体积通常也更大。但 AOF 的丢失窗口远小于 RDB,数据更安全,也完全兼容主从复制。流水账全但不耐念,照片好翻但会漏账。

Q8. 生产环境中,Redis 官方推荐的持久化方案是哪种?

单选题 · easy

  • A.开启混合持久化 RDB+AOF← 正确答案
  • B.只开启 RDB 快照持久化
  • C.只开启 AOF 日志持久化
  • D.关闭持久化仅依赖主从

答案:A。4.0 之后官方推荐 aof-use-rdb-preamble 的混合持久化:RDB 打底保证恢复快,AOF 增量保证丢得少,兼顾两者优点。只开一种都有明显短板,完全关闭持久化则把数据安全全部押在内存上,风险极高。

Q9. 下列关于 Redis 重启数据恢复的说法,正确的有哪些?

多选题 · mid

  • A.开启 AOF 时默认优先用 AOF 恢复← 正确答案
  • B.RDB 恢复速度通常快于 AOF 方式← 正确答案
  • C.混合 AOF 文件内含 RDB 前缀← 正确答案
  • D.混合模式需要加载两个独立文件

答案:ABC。默认配置下只要 AOF 开启,重启就只加载 AOF;混合持久化的 “AOF“ 文件本身带 RDB 前缀,所以加载它既快又全,并不需要额外加载第二个独立文件。D 错在”两个独立文件”,实际是一个文件里分两段。

Q10. 一个 10GB 内存的 Redis 实例执行 BGSAVE,关于 fork 阶段对父进程的影响,说法正确的是?

单选题 · hard

  • A.阻塞时长与内存和 CPU 相关,通常毫秒级← 正确答案
  • B.阻塞时长与磁盘写入速度成正比
  • C.子进程生成期间父进程完全不处理请求
  • D.子进程生成期间父进程等待其全部完成

答案:A。fork 的耗时主要取决于内存大小(页表复制量)和 CPU 速度,通常在毫秒到几十毫秒级,与磁盘写入速度无关,因为写文件由子进程异步完成。父进程只是瞬间停顿,随即继续服务。请分身只需要一眨眼,分身拍照的过程不耽误主人干活。