土法炼钢兴趣小组的算法知识备份

【Redis / 缓存内核】AOF:命令日志 · rewrite · appendfsync

文章导航

分类入口
databasestorage
标签入口
#redis#aof#appendfsync#rewrite#persistence#fsync#7.4

目录

第 9 篇 的快照在 fork 时刻冻结数据;AOF(Append Only File)走另一条路:把改变数据的命令按 RESP 追加到日志,重启时伪客户端重放。粒度更细,可配置更接近「每条写都落盘」,但文件膨胀与 rewrite 尖刺是代价。

本文锚定 Redis OSS 7.4.x aof.c 的多段清单(manifest)模型与 flushAppendOnlyFile()。丢失窗口与 fsync 语义必须和 fsync 失败的真相 一起读——appendfsync always 下写错误/ fsync 错误会 exit(1),不是重试能救。

本文是「Redis / 缓存内核」系列第 10 篇(共 16 篇)。→ 系列目录

版本锚定:Redis OSS 7.4 / 8.x;多段 AOF(base + incr)为默认组织方式。


一、问题:命令日志记录「增量」,不是状态

AOF 的思想与数据库 WAL / 逻辑日志 同构:先记录操作,崩溃后通过重放恢复。与 RDB 对比:

维度 RDB AOF
内容 序列化对象 SET/HSET/… RESP 文本
体积 紧凑 随时间膨胀
恢复 rdbLoad 重放命令(可能较慢)
丢失窗口 两次 BGSAVE appendfsync 决定

7.4 默认不把全部历史写进单个 appendonly.aof,而用 AOF manifest 管理:

命名示例(aof.c 注释):appendonly.aof.1.base.rdbappendonly.aof.1.incr.aof


二、写入路径:feedAppendOnlyFile → flushAppendOnlyFile

写命令成功修改内存后,feedAppendOnlyFile(dictid, argv, argc) 把命令序列化进 server.aof_bufcatAppendOnlyGenericCommand 生成 *argc\r\n$len\r\n... 格式)。DB 切换时自动插入 SELECT

主循环在 beforeSleep 等时机调用 flushAppendOnlyFile(force)

  1. write(aof_fd, aof_buf) 进内核页缓存;
  2. appendfsync 决定是否/何时 fsync
flowchart LR
  cmd["write command OK"]
  feed["feedAppendOnlyFile"]
  buf["server.aof_buf"]
  flush["flushAppendOnlyFile"]
  disk["page cache → disk"]
  cmd --> feed --> buf --> flush --> disk

aof_rewrite_incremental_fsync 控制 rewrite 子进程阶段性 fsync,降低单次 fsync 体积。

2.1 Manifest 与多文件协同

7.4 启动时 aofLoadManifestFromDisk() 读取 appendonly.aof.manifest 索引,确定当前 BASE 与有序 INCR 列表。在线写入只追加到最新 INCR;rewrite 成功后原子切换 BASE、把旧 BASE 标为 HISTORY 并异步删除。父进程在子进程 rewrite 期间仍向旧 INCR 追加,并把新命令同时写入 aof_rewrite_buf_blocks,防止 fork 窗口丢命令——这是混合持久化能无缝衔接的工程关键(与 第 11 篇 的 preamble 切换共用同一套 manifest 逻辑)。


三、appendfsync 三档与丢失窗口

模式 行为 典型丢失窗口 延迟
always 每次 flushredis_fsync(Linux 上常为 fdatasync 已应答复客户端的写,若 fsync 成功则≈0;失败则进程退出 最高
everysec 每秒后台 aof_background_fsync;写入可推迟最多约 2s 若上次 fsync 未完成 最多约 1 秒 已确认写(官方文档口径);灾难断电可能多丢 fsync 未完成缓冲 默认折中
no 由 OS 刷盘 可达数十秒或更多 最低

3.1 everysec 细节

flushAppendOnlyFileAOF_FSYNC_EVERYSEC 下:

这与 数据完整性 中「write 成功 ≠ 持久化」一致:everysec 承诺的是秒级,不是事务级。

3.2 always 的失败语义

aof.c 明确:若 appendfsync alwaysAOF write 或 fsync 失败,调用 exit(1)——因为回复可能已发给客户端,无法回滚内存状态。这与 PostgreSQL 在 fsync 失败后 PANIC 同族(见 fsyncgate):不能靠重试 fsync 假装成功

everysec / no 下写失败会设 aof_last_write_status = C_ERR 并暂停接受写,保留 buffer 待恢复。


四、AOF rewrite:压缩日志

命令日志对同一 key 多次 SET 会留下多条记录;rewrite 在子进程遍历当前内存,生成等价的最小命令集(或 RDB preamble,第 11 篇)。

rewriteAppendOnlyFileBackground()

  1. fork 子进程;
  2. 子进程 rewriteAppendOnlyFilerewriteAppendOnlyFileRio 写临时文件;
  3. 父进程继续把新命令追加到 旧 INCR + rewrite 缓冲aof_rewrite_buf_blocks);
  4. 子进程结束后,父进程把累积增量追加到新 BASE,切换 manifest。

触发:

尖刺:rewrite 与 BGSAVE 一样占用 fork 与磁盘 IO;no-appendfsync-on-rewrite yes 可在子进程期间跳过 fsync 以换吞吐(牺牲该窗口内最高耐久)。

rewriteAppendOnlyFileRio 对每 key 生成 SET/RPUSH/… 再跟 PEXPIREAT,并在 fork 子进程中对已 dump 对象 dismissObject() 减 COW——与 RDB 子进程优化同思路。


五、加载与校验

启动时 loadAppendOnlyFile 按 manifest 顺序:先 BASE 再 INCR。纯 AOF BASE 重放 RESP;RDB 格式 BASE 走 rdbLoad(混合持久化)。

aof-load-truncated yes 允许截断损坏尾部后启动——显式承认可能丢尾部命令redis-check-aof 工具做离线修复。

aof-timestamp-enabled 时 AOF 可带 #TS: 行标注 Unix 秒,便于审计命令边界,不改变重放语义。


六、学术谱系与争论

谱系:ARIES WAL → Redis antirez AOF(2010 年代)→ 4.x fsync 策略固化 → 7.x 多段 manifest + 可选 RDB preamble。

争论

  1. always vs everysec:always 贴近「存储」语义;everysec 是缓存/会话主流,但断电丢 1 秒是设计内建,不是小概率事故。
  2. rewrite 频率:过小文件浪费 fd 与 manifest 切换;过大文件 rewrite 时间长、恢复慢。auto-aof-rewrite-percentage 100 表示翻倍触发,需结合写入速率调。
  3. AOF 与复制WAIT/WAITAOF 把复制与本地 fsync 绑定;AOF offset 与 fsynced_reploff_pending 在模块 NO_AOF 路径有边角(flushAppendOnlyFile 注释)。

开放问题:NVMe + 高 hzeverysec 的尾延迟与「1 秒丢失窗口」在 kube 强制 kill 场景是否可接受,需业务 RPO 定义,非 Redis 单点能答。


七、常见误解

7.1 「AOF 开着就不会丢数据」

只有 appendfsync always 且 fsync 成功才接近「每条写持久」;everysec 明确允许丢约 1 秒。

7.2 「rewrite 会丢数据」

不会丢逻辑状态;rewrite 生成与当前内存等价的 BASE。fork 窗口内的增量由父进程缓冲并在结束后追加。

7.3 「fsync 失败重试就好」

在 always 模式下错误;见 fsyncgate。Redis 选择退出或停写,而非假装成功。

7.4 「AOF 比 RDB 恢复总是更慢」

纯命令 AOF 常更慢;RDB preamble 的 BASE 使恢复接近 RDB 速度(第 11 篇)。


八、小结

三句话小结

  1. AOF 把变更命令追加到 manifest 管理的 BASE/INCR 文件,恢复靠重放或加载 RDB 型 BASE。
  2. appendfsync 在 always / everysec / no 之间划分耐久与延迟;everysec 的丢失窗口约 1 秒是协议一部分。
  3. rewrite fork 子进程压缩日志;与 BGSAVE 互斥子进程槽,并共享 COW/fork 代价。

上一篇RDB 快照

下一篇混合持久化与崩溃语义


参考资料

  1. Redis OSS 7.4 源码:src/aof.cfeedAppendOnlyFileflushAppendOnlyFilerewriteAppendOnlyFileBackgroundgetNewBaseFileNameAndMarkPreAsHistory)、src/config.cappendfsyncaof-use-rdb-preamble)。
  2. Redis Documentation v7.x, PersistenceAOFfsync policy
  3. fsync 失败的真相数据完整性
  4. 系列 index第 11 篇混合持久化

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-13 · database / storage

【Redis / 缓存内核】混合持久化与崩溃语义

说明 Redis 7.4 默认开启的 RDB preamble + INCR AOF 如何组合恢复路径,结合 fsyncgate 与数据完整性给出 appendfsync 与混合模式的配置决策树及耐久争论边界。


By .