RDB 是全量二进制快照,恢复快,丢两次快照之间的数据。AOF 是命令日志,appendfsync 决定丢多少。重写 fork 子进程读内存,不读旧日志。生产混着开,但没有任何方案能零丢失。
面试官问"持久化方式",其实在等三层答案
"Redis 的持久化方式有哪些"------背出 RDB 和 AOF 两个词,只是入场券。面试官往下一戳,人就冒汗了。
这道题真正在考三层:两种持久化的底层执行逻辑(BGSAVE 里 fork 到底干了什么、AOF 重写读的是内存还是旧文件);出故障时丢多少数据;线上怎么搭。
只背名字的人,卡死在第二层。
RDB:SAVE 和 BGSAVE 根本不是一回事
RDB 干的事很直白:把内存全量 dump 成一份紧凑二进制快照落盘。触发有三条路------手动 SAVE、手动 BGSAVE、配置定时触发。
分水岭在 SAVE 和 BGSAVE:
SAVE 是主线程自己干,大内存实例能把整个 Redis 钉死。生产环境别用。
BGSAVE fork 出子进程,主线程接着处理请求------这才是线上跑的那条路。但 fork 不免费:复制页表,内存越大越慢,fork 那一瞬主线程仍然阻塞,只是窗口比 SAVE 短得多。
| 维度 | SAVE | BGSAVE |
|---|---|---|
| 执行线程 | 主线程 | fork 出的子进程 |
| 阻塞范围 | 整个写盘过程 | 仅 fork 阶段 |
| 大内存影响 | 秒级阻塞,不可接受 | fork 阶段短暂阻塞 |
| 生产可用 | 否 | 是 |
RDB 的优势实打实:文件紧凑,恢复极快。几十 GB 的实例加载 RDB,往往比回放 AOF 快一个数量级,天生适合冷备和跨机房搬运。
短板同样致命。它是间隔性快照------两次快照之间写进来的数据,机器一当机直接蒸发。丢多少看你配的间隔,可能几分钟,也可能几万条写命令。
AOF 的可靠性,全押在 appendfsync 一个参数上
AOF 走另一条路:每条写命令追加进日志,重启时回放,把数据"演"回来。
可靠性开关就是 appendfsync,三个值对应三个量级的丢数据:
| 策略 | 刷盘时机 | 最多丢多少 | 性能 |
|---|---|---|---|
always |
每条写命令立刻 fsync | 几乎不丢 | 最差,磁盘 IO 打满 |
everysec |
后台线程每秒 fsync 一次 | 最多 1 秒数据 | 生产最常用 |
no |
交给操作系统决定 | 不可控,可能丢几十秒 | 最快,但没人敢赌 |
线上配置大致长这样:
conf
# RDB 定时快照:三个条件任一满足即触发
save 900 1 # 900 秒内至少 1 次修改
save 300 10
save 60 10000
# AOF
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 混合持久化:4.0 之后支持,AOF 文件头用 RDB 格式
aof-use-rdb-preamble yes
everysec 是和稀泥的选项:把丢数据上限锁死在 1 秒,又没把性能拖垮。绝大多数生产实例都用它。
AOF 重写:这里读的不是旧日志
AOF 有个天然毛病:命令越写越多,文件越来越大。一个计数器自增 100 万次,日志里躺着 100 万条 INCR,内存里其实只有一个数字。
重写来解决这个。而这里有个人人答错的点------重写不读旧 AOF 文件。
子进程读内存里的当前数据,按最终状态反推一份最短命令集。被覆盖的写、已删除的键、可合并的批量操作,全部丢掉。
重写期间新的写命令不能丢,主线程同时往 AOF 缓冲区和重写缓冲区里写。子进程写完,把重写缓冲区追加上去,再原子替换旧文件。整个过程不阻塞主线程。
被追问"重写为什么安全",答这一句就够:内存快照 + 增量缓冲区,两头都不丢。
单开一个行不行?行,先看代价
| 组合 | 丢数据风险 | 恢复速度 | 典型场景 |
|---|---|---|---|
| 只开 RDB | 高,两次快照之间的全丢 | 快 | 纯缓存、数据可重建 |
| 只开 AOF | 低,everysec 最多 1 秒 |
慢,回放命令 | 数据不能丢、能忍恢复慢 |
| RDB + AOF 混合 | 低 | 快,先加载 RDB 再补增量 AOF | 生产默认选择 |
生产上的主流答案是两个一起开:AOF 把丢失量压到最小,RDB 在重启、迁移、冷备时提供一条快速通道。
这儿必须纠正一个流传很广的误区:开了持久化不等于零丢失 。哪种方案都有丢数据的可能,区别只是量级。everysec 丢最多 1 秒,always 号称不丢,但 fsync 完成前掉电依然有窗口,RDB 丢的是整个时间窗。
能主动说出"没有方案能做到绝对零丢失",比背出两种持久化的定义值钱得多。
面试连环追问
Q:SAVE 和 BGSAVE 区别? A:前者主线程执行、全程阻塞;后者 fork 子进程,主线程只在 fork 阶段短暂阻塞。
Q:BGSAVE 期间的新写入会丢吗? A:不会。fork 的 copy-on-write,子进程看到的是 fork 那一刻的内存快照。
Q:always 就绝对不丢数据了? A:几乎不丢,但性能代价极大,极端掉电仍有窗口。别把"几乎"说成"绝对"。
Q:AOF 文件越来越大怎么办? A:fork 子进程读内存生成新 AOF,去掉冗余命令,不读旧文件,重写期间增量写进重写缓冲区。
Q:线上你怎么配? A:RDB + AOF 混合开,appendfsync everysec,用 INFO persistence 盯 rdb_last_bgsave_status 和 aof_last_bgrewrite_status。
一句话收束
只能留一段话背下来,就背这段:
Redis 持久化有 RDB 全量快照和 AOF 命令日志两种。RDB 靠 fork 子进程生成二进制快照,恢复快、适合冷备,但会丢时间窗内的数据,大内存 fork 有短暂阻塞。AOF 记录每条写命令,用
always / everysec / no三种刷盘策略控制丢失量级,用重写机制压缩文件体积,重写读内存而非旧日志。生产环境建议两者配合,按业务对丢失的容忍度选刷盘策略,同时清楚没有任何方案能做到零丢失。
写在最后
这道题难的地方从来不是记住 RDB、AOF 两个词,是把底层机制和线上取舍串成一条线。
顺便问一句:你们线上是 everysec 还是直接上了 always?有没有遇到过 AOF 重写把内存打爆、或者 fork 卡住主线程的事故?评论区聊聊你们的配置和踩过的坑。
有用的话点个赞。