Skip to content

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 是写后日志?

  1. 避免语法检查开销 — 如果先写日志再执行,语法错的命令也要写。AOF 写后只记录正确的命令
  2. 不阻塞命令执行 — 命令执行完再写日志,不会因为写盘慢而阻塞命令的响应

代价: 命令执行后、写 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 1000

rewrite 的原则:

  • 一个 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 文件修复:

bash
redis-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:0

4.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 QPS

4.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 阻塞的诊断方法
  • ← 返回 **持久化索引**

Knowledge4J — Java 知识库