Redis 持久化 RDB 与 AOF
候选人小钱在美团的二面中,面试官问道:
"Redis 的持久化机制是怎样的?RDB 和 AOF 有什么区别?"
小钱说:"RDB 是定期快照,AOF 是写操作日志。"面试官追问:"RDB 的快照是怎么生成的?fork() 是什么?COW 呢?"
小钱愣了一下,说:"fork 是复制进程...COW 是写时复制..."
面试官继续:"AOF 的 fsync 策略有哪些?everysec 真的是每秒刷盘吗?"
小钱开始语无伦次。
【面试官心理】
这道题我用来区分"会用"和"会用且懂原理"的候选人。知道 RDB 和 AOF 区别的占 80%,能解释 fork() 和 COW 的占 30%,能说清 AOF fsync 策略的占 20%。持久化是 Redis 面试的高频深水区,因为涉及操作系统底层知识,很多人都会在这里露馅。
一、RDB 快照 🔴
1.1 问题拆解
RDB 的核心原理:fork() + COW(Copy-On-Write)
graph TD
A["主进程"]
B["fork() 创建子进程"]
C["子进程生成 RDB 文件"]
D["COW: 共享父进程内存页"]
E["子进程完成,替换 RDB 文件"]
A --> B
B --> C
B --> D
D --> C
C --> E
1.2 fork() 是什么?
fork() 是 Linux 的系统调用,用于创建子进程:
// fork() 的特点:调用一次,返回两次
pid_t pid = fork();
if (pid == 0) {
// 子进程:开始生成 RDB
redisFork(CHILD_TYPE_RDB);
} else if (pid > 0) {
// 父进程:继续处理请求
// ... 处理客户端命令 ...
} else {
// fork 失败
}
fork() 在 Linux 中是写时复制的:
- fork() 之后,父子进程共享同一份物理内存
- 只有当某个进程尝试修改内存页时,才会复制该页给子进程
1.3 RDB 的生成时机
// redis.conf 配置
save 900 1 // 900 秒内 ≥1 次写入
save 300 10 // 300 秒内 ≥10 次写入
save 60 10000 // 60 秒内 ≥10000 次写入
// 关闭自动保存
save "" // 只有手动 BGSAVE
# 手动触发 RDB 快照
redis-cli BGSAVE # 后台异步保存
redis-cli SAVE # 同步保存(会阻塞)
# 查看最近一次 RDB 保存时间
redis-cli LASTSAVE
1.4 ❌ 错误示范
候选人原话:"RDB 就是 Redis 把数据复制一份存到磁盘上。"
问题诊断:
- 不理解 fork() 的 COW 机制
- 不知道 RDB 是子进程生成的而不是主进程
- 不理解 COW 对性能的影响
面试官内心 OS:"这个候选人肯定没有在生产环境中经历过 Redis 的 fork 延迟问题。fork() 在数据量大时可能阻塞主进程数秒,这是生产中的大坑。"
二、AOF 日志 🔴
2.1 AOF 的核心原理
AOF(Append Only File)将每个写命令追加到文件末尾:
graph TD
A["SET name '张三'"]
B["INCR view_count"]
C["HSET user age 28"]
D["aof 文件"]
A -->|"APPEND"| D
B -->|"APPEND"| D
C -->|"APPEND"| D
2.2 AOF 文件格式
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$6\r\n张三\r\n
*3\r\n$5\r\nINCR\r\n$10\r\nview_count\r\n
*3\r\n$4\r\nHSET\r\n$4\r\nuser\r\n$3\r\nage\r\n$2\r\n28\r\n
这是 Redis 的 RESP(REdis Serialization Protocol)协议,是二进制安全的。
2.3 AOF 的 fsync 策略 🔴
这是面试的高频深水区。
// redis.conf 配置
appendonly yes
appendfsync always // 每次写都刷盘
appendfsync everysec // 每秒刷盘一次
appendfsync no // 让操作系统决定
追问:everysec 真的是每秒刷盘吗?
// AOF 伪代码 (aof.c)
if (server.unixtime - server.last_fsync > 1) {
// 只有超过 1 秒才触发刷盘
// 如果上一次 fsync 还没完成,本次跳过
fd = open(AOF_FILE, O_WRONLY);
write(fd, buf, len);
fsync(fd); // 同步刷盘
close(fd);
}
⚠️
everysec 不是"每秒精确刷盘一次",而是"每秒最多刷盘一次"。如果上一次 fsync 还没完成,本次就会跳过。这意味着最坏情况下可能丢失 2 秒的数据,而不是 1 秒。
2.4 标准回答
# AOF 的推荐配置
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100 # AOF 文件比上次大 100% 时重写
auto-aof-rewrite-min-size 64mb # AOF 文件至少 64MB 时才重写
【面试官心理】
AOF fsync 策略是 Redis 面试的经典深水区。能说出三种 fsync 策略的占 60%,能解释 eachsec 的具体实现的占 20%,能说出"上一次未完成就跳过"这个细节的占 5%。这个细节能说出来的,基本都看过 Redis 源码。
三、RDB vs AOF 对比 🟡
3.1 优缺点对比
3.2 何时选 RDB?
适合场景:
- 数据备份:每天凌晨 RDB 全量备份
- 容灾恢复:RDB 文件小,便于传输
- 允许数据丢失:缓存场景,数据可以从 DB 重新加载
- 数据量中等:数据量小于 10GB
3.3 何时选 AOF?
适合场景:
- 数据安全性要求高:不能接受数据丢失
- 作为数据库而非缓存:需要持久化保证
- 数据量较大:RDB 恢复太慢,AOF 可以逐步追加
- 写操作频繁:append-only 模式性能更好
【面试官心理】
这道对比题我想考察的是候选人的"方案选型能力"。能列出对比表的占 60%,能结合场景讲清楚选哪个的占 30%。生产环境中,Redis 通常同时开启 RDB 和 AOF——RDB 做定期备份,AOF 做数据安全保证。
四、AOF 重写 🔴
4.1 AOF 文件为什么会变大?
// AOF 文件内容
INCR counter // counter: 1
INCR counter // counter: 2
INCR counter // counter: 3
...(1万次 INCR)
INCR counter // counter: 10000
// AOF 重写后
SET counter 10000 // 一个命令替代 1 万条
4.2 AOF 重写原理
graph TD
A["主进程接收命令"]
B["追加到 AOF 缓冲区"]
C["AOF 重写子进程 (bgrewriteaof)"]
D["子进程遍历内存生成新 AOF"]
E["子进程完成,发送信号"]
F["主进程将缓冲写入新 AOF"]
G["新 AOF 替换旧 AOF"]
A --> B
A --> C
C --> D
D --> E
E --> F
F --> G
# 手动触发 AOF 重写
redis-cli BGREWRITEAOF
4.3 AOF 重写的原子性保证
AOF 重写过程中,新命令如何不丢失?
// Redis 使用父子进程 + AOF 缓冲区的设计
if (has_child) {
// 子进程正在重写,将新命令追加到 AOF 缓冲区
aofRewriteBufferAppend(server.replica_repl_buf);
} else {
// 正常追加到 AOF 文件
write(aof_fd, buf, len);
}
子进程重写完成后,主进程会:
- 将 AOF 缓冲区的内容追加到新 AOF 文件
- 用新 AOF 文件替换旧 AOF 文件
这是 Redis 实现无锁重写的核心技巧。
4.4 ❌ 错误示范
候选人原话:"AOF 重写就是删除旧的 AOF 命令。"
问题诊断:
- 完全不理解 AOF 重写的原理
- 不知道重写是在子进程中进行的
- 不理解 AOF 缓冲区的存在
【面试官心理】
AOF 重写是 Redis 中比较复杂的机制,涉及进程间通信和文件系统操作。能说清楚父子进程 + 缓冲区设计的占 10%,知道"无锁重写"概念的占 15%。
五、生产避坑
:::warning ⚠️
生产环境中的三大翻车点:
-
fork() 阻塞:Redis 数据量超过 10GB 时,fork() 可能阻塞主进程数秒到数十秒,导致 Redis 假死。在大数据量场景下,这是生产事故的高发区。
-
AOF 文件过大:没有配置 AOF 重写策略,AOF 文件膨胀到数百 GB,恢复时间长达数小时。
-
everysec 数据丢失:误以为 everysec 完全安全,实际上最坏可能丢 2 秒数据。
:::
排查方法:
# 查看 RDB/AOF 配置
redis-cli CONFIG GET save
redis-cli CONFIG GET appendonly
redis-cli CONFIG GET appendfsync
# 查看 AOF 文件大小
redis-cli INFO persistence | grep aof
# 查看 fork() 耗时
redis-cli INFO stats | grep latest_fork_usec
# 查看 AOF 重写状态
redis-cli INFO persistence | grep -E "aof_rewrite|child"
:::tip 💡
生产最佳实践:
- 数据量 < 10GB:同时开启 RDB 和 AOF,RDB 做备份,AOF 做安全保证
- 数据量 > 10GB:只开 AOF,使用 everysec 策略,并配置 AOF 重写
- 开启 RDB + AOF 时,Redis 重启优先加载 AOF(数据更完整)
- fork() 延迟超过 1 秒时,考虑升级 Redis 版本或增加机器内存
:::
【面试官心理】
这道题我想最终验证的是候选人的"生产实战经验"。能把 RDB 和 AOF 的原理讲清楚的占 40%,能说出 fork() 阻塞问题的占 20%,能在面试中主动提到生产配置建议的占 10%。一个有深度的 Redis 候选人,应该既懂原理,又踩过坑。