03.2 - AOF 日志机制
定位: Redis 的增量持久化——通过追加写命令日志来保证数据不丢 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: 写后日志设计、三种 fsync 策略、BGREWRITEAOF rewrite 流程、AOF 阻塞风险
一、这是什么?为什么需要它?
是什么
AOF(Append Only File)是 Redis 的增量持久化机制,将每条修改数据的命令追加写入到 AOF 文件中。重启时通过重放 AOF 文件中的命令来恢复数据。
为什么需要 AOF?
RDB 的缺陷: RDB 保存的是"瞬间快照":
- save 900 1 → 900秒内只有1次修改就存
- 如果第 899 秒宕机,这 899 秒的所有修改全丢
- 场景:电商大促,每秒 10 万次写入
- 900 秒 ≈ 9 千万次写入可能丢失
AOF 的价值: AOF 保存的是"操作日志":
- 每条 SET/LPUSH/... 命令写入文件
- appendfsync everysec → 最多丢 1 秒数据
- 安全性与 RDB 的组合使用:
- RDB 做全量 + AOF 做增量
二、原理拆解
2.1 写后日志——为什么 AOF 是"写后日志"?
MySQL binlog 是写前日志(WAL - Write Ahead Log):
先写日志 → 再写数据(便于回滚和 crash-safe)
Redis AOF 是写后日志:
先执行命令(修改内存)→ 再写 AOF 日志为什么 AOF 是写后日志?
- 避免语法检查开销 — 如果先写日志再执行,语法错的命令也要写。AOF 写后只记录正确的命令
- 不阻塞命令执行 — 命令执行完再写日志,不会因为写盘慢而阻塞命令的响应
代价: 命令执行后、写 AOF 前若宕机,会丢这条命令。但最多丢 1 秒(与 fsync 策略有关)
2.2 三种 fsync 策略
磁盘写入过程:
- 调用 write() → 写入 OS 内核缓冲区(很快)
- 调用 fsync() → 刷到磁盘(很慢,磁盘 I/O)
everysec 的实现: 主线程 write() → 写入缓冲区(不阻塞),后台线程每秒执行一次 fsync() 但如果 fsync 线程被卡住(磁盘 I/O 饱和):缓冲区积累 → soft limit → 主线程阻塞等待
2.3 AOF Rewrite——如何进行瘦身?
为什么需要 rewrite?
AOF 是追加写入 → 不断增长
一个 key SET 1000 次:
SET key 1 → SET key 2 → ... → SET key 1000
→ AOF 里 1000 条 SET 命令
→ 恢复时要执行 1000 次 SET
→ 实际只需要最后一条 SET key 1000rewrite 的原则:
- 一个 key 只保留最后一条命令
- 原始的 1000 条 SET counter 1/2/.../1000 → rewrite 后只剩:SET counter 1000
- 对于 list/hash/zset/set:LPUSH/RPUSH 合并为一个命令
- 效果:AOF 文件大小减少 ~80-90%
自动触发条件:
auto-aof-rewrite-percentage 100 # 比上次大一倍时触发
auto-aof-rewrite-min-size 64mb # 最少 64MB 才触发2.4 AOF 的阻塞风险
通过 INFO persistence 监控:
- aof_delayed_fsync: 0(如果 >0,说明发生过 AOF 阻塞)
三、图解全景
写操作流程:
恢复(启动时):
AOF 文件修复:
bashredis-check-aof --fix appendonly.aof ``` → 截断最后一个不完整的命令
四、实战验证
4.1 查看 AOF 内容
bash
# 启用 AOF
redis-cli CONFIG SET appendonly yes
# 执行一些命令
redis-cli SET a 1
redis-cli LPUSH list x y z
# 直接查看 AOF 文件
cat /var/lib/redis/appendonly.aof | strings | head -20
# *3
# $3
# SET
# $1
# a
# $1
# 1
# 用 redis-cli 查看 AOF 统计
redis-cli INFO Persistence | grep aof
# aof_enabled:1
# aof_current_size:1234
# aof_base_size:1000
# aof_pending_rewrite:0
# aof_delayed_fsync:04.2 验证 fsync 策略差异
bash
# always 策略(最安全但最慢)
redis-cli CONFIG SET appendfsync always
redis-benchmark -n 100000 -t SET -q
# SET: ~500 QPS ← 每次写入都 fsync
# everysec 策略(推荐)
redis-cli CONFIG SET appendfsync everysec
redis-benchmark -n 100000 -t SET -q
# SET: ~100000 QPS ← 每秒 fsync 一次
# no 策略(最快)
redis-cli CONFIG SET appendfsync no
redis-benchmark -n 100000 -t SET -q
# SET: ~120000 QPS4.3 触发 AOF Rewrite
bash
# 制造大量数据触发 rewrite
for i in $(seq 1 100000); do
redis-cli SET "key:$i" "value:$i" > /dev/null
done
# 手动触发 rewrite
redis-cli BGREWRITEAOF
# Background append only file rewriting started
# 观察 rewrite 状态
redis-cli INFO persistence | grep aof_rewrite
# aof_rewrite_in_progress:0
# aof_last_rewrite_time_sec:3 # rewrite 耗时 3 秒
# aof_current_size:4263424 # rewrite 前 4MB
# aof_base_size:32435 # rewrite 后 32KB
# 文件大小对比
ls -lh /var/lib/redis/appendonly.aof # 当前(可能增长中)
ls -lh /var/lib/redis/temp-rewriteaof-* # rewrite 中的临时文件五、面试视角
| 追问 | 答案要点 |
|---|---|
| AOF 是写前日志还是写后日志? | 写后日志:先执行命令修改内存,再记录到 AOF。避免了语法错误的命令写入日志 |
| 三种 fsync 策略怎么选? | 生产推荐 everysec:always 太慢(~100 QPS),no 可能丢 30 秒数据 |
| AOF rewrite 的时机? | 自动:文件比上次大了一倍且 ≥64MB;手动:BGREWRITEAOF |
| AOF 阻塞的原因? | fsync 线程卡住(磁盘 I/O 饱和)→ 缓冲区积累 → 超阈值 → 主线程阻塞 |
| AOF 文件损坏了怎么办? | redis-check-aof --fix 修复(截断最后一个不完整命令) |
| AOF 和 RDB 可以同时开启吗? | 可以(混合持久化),Redis 重启优先加载 AOF,加载速度快于 RDB |
| AOF rewrite 时父进程还处理请求吗? | 处理。fork 子进程重写,同时父进程将新命令写入重写缓冲区,完成后合并 |
📚 相关链接
- **RDB 快照机制** — AOF 的互补方案
- **混合持久化** — RDB + AOF 一起用的最佳实践
- **性能诊断** — AOF 阻塞的诊断方法
- ← 返回 **持久化索引**