Redis 的数据主要保存在内存中。进程退出、操作系统崩溃或机器断电后,只靠内存无法恢复数据。
AOF(Append Only File)通过持续记录会改变数据集的操作,让 Redis 重启时能够重新构造数据。不过,AOF 并不等于"绝不丢数据"。它真正解决的是:把内存中的状态转换为可以落盘、重放和恢复的持久化记录,并允许使用者在性能与耐久性之间选择。

一、AOF 记录的是什么
1. 只记录会改变数据集的操作
AOF 不会把所有客户端请求都保存下来。GET、EXISTS 这类只读命令没有必要参与数据恢复;SET、HSET、INCR、DEL 等会改变数据状态的操作才需要被持久化。
Redis 记录的是可重放的命令效果,但日志中的命令不一定与客户端发送的原始文本完全相同。Redis 可能为了确定性、过期处理或内部传播语义,将操作转换成等价的可重放命令。
2. AOF 使用 RESP 编码
假设执行:
text
SET testkey testvalue
对应的 RESP2 编码类似:
text
*3
$3
SET
$7
testkey
$9
testvalue
其中:
*3表示数组中有三个元素。$3、$7、$9表示后续字符串的字节长度。- AOF 可以按 Redis 协议解析并重放,而不是依赖自然语言形式的日志。
3. AOF 是写后日志,但不能误解为"完全不阻塞"
Redis 会先校验并执行命令,成功修改内存数据后,再把需要持久化的效果追加到 AOF 缓冲区。执行失败的命令不会作为有效数据变更写入 AOF,因此恢复时不会重放一条本来就无法成功执行的命令。
"写后日志"只表示日志生成发生在命令执行之后,不表示 AOF I/O 永远不会影响当前请求:
appendfsync always会在回复客户端之前等待 AOF 写入和fsync完成。appendfsync everysec的fsync通常由后台线程执行,但主线程的write(2)仍可能因磁盘或内核回写压力而阻塞。- AOF 文件切换、
fork和写时复制也可能造成延迟尖峰。
二、从执行成功到真正落盘,中间有四层
一条写命令成功执行后,并不会瞬间到达持久化介质。完整链路可以拆成四层:
- 内存数据集:命令已经改变 Redis 中的数据。
- Redis AOF 缓冲区:命令的持久化表示等待写入文件。
- 操作系统页缓存 :
write(2)已经把数据交给内核,但数据未必到达磁盘。 - 持久化介质 :
fsync或fdatasync完成后,Redis 才能按当前硬件和文件系统提供的语义认为数据已持久化。
不同故障会影响不同层:
| 故障类型 | 可能丢失的内容 | AOF 能否单独解决 |
|---|---|---|
| Redis 进程崩溃,操作系统仍正常 | 尚未写入内核的 AOF 缓冲区 | 取决于写回策略和当时状态 |
| 操作系统崩溃或机器断电 | 尚未完成 fsync 的页缓存数据 |
取决于 appendfsync 策略 |
| 磁盘损坏、云盘丢失 | 本机全部持久化文件 | 不能,需要副本和异机备份 |
误执行 FLUSHALL、错误业务写入 |
正确数据被合法命令覆盖或删除 | AOF 会忠实记录错误,需要备份或时间点恢复方案 |
所以,AOF 提升的是单实例本地持久化能力,不等于备份,也不等于跨节点强一致。
三、三种 appendfsync 策略

启用 AOF:
conf
appendonly yes
Redis 配置文件默认是 appendonly no,也就是默认不启用 AOF。启用后,appendfsync 决定 Redis 主动要求操作系统把 AOF 数据同步到持久化介质的频率。
1. appendfsync always
conf
appendfsync always
每批新 AOF 数据写入后都执行 fsync,完成后才回复客户端。多个并发请求或 Pipeline 中的一批命令可能共享一次写入和 fsync,但它仍然是三种策略中延迟最高的一种。
适合:单条已确认写入的丢失成本很高,并且能够接受磁盘同步延迟的场景。
边界:它只能确认本机 AOF 已按系统语义落盘,不能防止磁盘整体损坏,也不能自动保证故障转移后的新主节点一定包含该写入。
2. appendfsync everysec
conf
appendfsync everysec
这是 appendfsync 的默认值,也是最常见的折中方案。Redis 通常每秒由后台 I/O 线程执行一次 fsync。发生机器级故障时,通常可能丢失最近约一秒的数据;遇到严重磁盘阻塞或调度延迟时,不应把"一秒"理解成绝对不变的硬上限。
主线程仍负责把 AOF 缓冲区通过 write(2) 交给内核。当上一次后台 fsync 长时间未结束时,Redis 会暂缓部分写入,但最终仍可能执行会阻塞的 write(2),形成延迟尖峰。
3. appendfsync no
conf
appendfsync no
Redis 仍然执行 write(2),但不主动调用 fsync,何时落盘由操作系统决定。它减少了 Redis 主动同步磁盘的开销,但数据丢失窗口受内核参数、文件系统和 I/O 压力影响,不能简单认为一定是某个固定秒数。
适合:数据可以从其他系统重建,或者业务更重视吞吐和延迟、能够接受更大丢失窗口的场景。
4. 策略不是只看平均吞吐
选择时至少需要同时观察:
- 平均延迟与 P99、P999 尾延迟。
- 故障时最多允许丢失多少已确认写入。
- 磁盘
fsync延迟是否稳定。 - 是否有副本、备份和故障转移。
- AOF 重写期间是否出现 I/O 竞争。
四、为什么 AOF 文件需要重写
AOF 按操作追加。同一个 Key 被修改一千次,日志中可能存在一千次历史操作,但恢复最新状态往往只需要少量命令。
例如:
text
SET counter 1
INCR counter
INCR counter
INCR counter
如果当前最终值为 4,新 AOF 只要能重建 counter = 4 即可,无须保留全部历史过程。
AOF 重写不是压缩旧文件,也不是逐行合并旧命令。它读取当前内存数据集,为当前状态生成一份新的基础表示。因此,重写可以:
- 删除已经失效的历史操作。
- 减小持久化文件体积。
- 减少重启时需要加载和重放的数据量。
- 控制长期运行实例的恢复时间。
自动重写常见配置为:
conf
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
含义是:AOF 至少达到 64 MB,并且相对上次重写后的基准大小增长 100% 时,可以触发自动重写。真实生产参数应根据写入速度、磁盘空间和可接受的重写频率调整。
五、AOF 重写不是后台线程,而是子进程
BGREWRITEAOF 的命令复杂度标记为 O(1),指的是发起动作本身,不表示整个重写只做常数工作。真正的重写需要遍历数据集并生成持久化文件。
Redis 在类 Unix 系统上通过 fork 创建子进程,由子进程执行重写。父进程继续处理客户端请求。
这里必须区分:
- 后台 I/O 线程可以承担
fsync、异步释放等任务。 - AOF 重写主体由子进程完成,不是普通后台线程。
fork发生在主线程,创建子进程的瞬间仍可能让事件循环停顿。
子进程最初通过写时复制(Copy-on-Write,COW)与父进程共享内存页。重写期间父进程继续修改数据时,相应内存页会被复制,导致额外内存占用和内存带宽开销。
六、Redis 7 之前与 Redis 7+ 的重写模型

1. Redis 7 之前:单文件 AOF 与重写差异缓冲区
旧版模型可以概括为:
- 子进程根据
fork时刻的数据集生成临时新 AOF。 - 父进程继续把新写入追加到正在使用的旧 AOF。
- 同期的新写入还会进入 AOF 重写差异缓冲区。
- 子进程主体完成后,父进程把差异追加到新 AOF,再原子替换旧文件。
原文常说的"一个拷贝、两处日志"描述的是这一代实现。它的核心目标是在重写期间继续服务,同时让新 AOF 包含重写期间发生的变更。
2. Redis 7+:多部件 AOF
从 Redis 7.0 开始,AOF 由多类文件共同组成:
- BASE 文件:最近一次成功重写生成的基础数据,可以是 RDB 或 AOF 格式。
- INCR 文件:BASE 之后发生的增量写命令,可以存在多个。
- manifest 文件:记录有效 BASE、INCR 文件及加载顺序。
重写开始时,父进程直接打开一个新的 INCR 文件继续记录后续写入;子进程根据当时的数据集生成新的 BASE 文件。完成后,Redis 生成临时 manifest,并通过原子替换让新 BASE 与相应 INCR 生效。
这种设计不再需要旧版 AOF 重写差异缓冲区。INFO persistence 中的 aof_rewrite_buffer_length 也从 Redis 7.0 起被移除。
3. 为什么 BASE 文件通常是 RDB 格式
现代 Redis 默认配置:
conf
aof-use-rdb-preamble yes
这会让 BASE 使用紧凑的 RDB 二进制表示,后续 INCR 继续记录 AOF 命令。好处是基础数据加载更快、体积通常更小,同时保留增量 AOF 的耐久性。
因此,看到 .base.rdb 并不表示 AOF 被关闭了;它可能正是 Redis 7+ 多部件 AOF 的基础文件。
七、重写"后台执行"为什么仍可能造成阻塞
重写主体虽然在子进程中运行,但父进程仍可能受到影响:
1. fork 停顿
fork 需要创建进程并复制页表。实例内存越大、页表越大或虚拟化环境越差,停顿越明显。LATENCY 监控中的 fork 事件可以帮助定位这类尖峰。
2. 写时复制内存
重写期间写流量越大,被修改的内存页越多,COW 额外内存越高。内存不足可能触发交换、OOM 或重写失败。
3. 磁盘 I/O 竞争
子进程持续写 BASE 文件,父进程还要写 INCR AOF、执行 fsync。两者竞争同一磁盘时,write(2) 和 fsync 延迟都可能升高。
默认开启的配置:
conf
aof-rewrite-incremental-fsync yes
会让重写子进程每生成约 4 MB 数据就逐步同步一次,避免最后一次性同步巨量数据造成更大的延迟尖峰。
4. no-appendfsync-on-rewrite 的耐久性代价
conf
no-appendfsync-on-rewrite no
默认值 no 表示重写期间仍遵守正常 appendfsync 语义。改成 yes 可以降低重写期间的磁盘同步竞争,但这段时间的耐久性会接近 appendfsync no,故障时可能丢失更多数据。
它不是免费的性能开关,必须按照允许的数据丢失窗口决定。
八、Redis 如何从 AOF 恢复
1. 启动加载顺序
Redis 7+ 启动时读取 manifest,先加载 BASE,再按顺序加载有效的 INCR 文件,最终还原数据集。
如果 RDB 和 AOF 同时开启,Redis 启动时优先使用 AOF,因为它通常包含比周期性 RDB 快照更新的数据。
2. 尾部截断与中间损坏不是一回事
机器故障可能让 AOF 最后一条命令只写了一部分。默认配置:
conf
aof-load-truncated yes
允许 Redis 在遇到尾部不完整记录时,加载前面完整的数据并启动,同时输出警告。
如果损坏发生在文件中间,Redis 通常会拒绝继续加载,因为无法可靠判断后续记录边界和数据正确性。此时应先复制原文件,再使用匹配 Redis 版本的 redis-check-aof 检查;--fix 会删除无法恢复的尾部内容,不能在没有备份的情况下盲目执行。
3. 恢复速度仍受数据量限制
AOF 能恢复数据,但不保证恢复很快。BASE 越大、INCR 越多,加载时间越长。控制重写频率、磁盘吞吐和文件数量,实际上是在控制恢复时间目标(RTO)。
九、关键写入如何获得更明确的持久化确认
Redis 7.2 起提供:
text
WAITAOF numlocal numreplicas timeout
例如:
text
SET order:1001 paid
WAITAOF 1 1 2000
它会等待当前连接之前的写入,被本地主节点以及指定数量的副本 fsync 到各自 AOF,或者等待超时。返回值包含实际完成本地和副本 fsync 的数量,客户端必须检查结果是否达到要求。
WAITAOF 可以提高关键操作的真实数据安全性,但仍不能把 Redis 变成严格的强一致数据库:超时、故障转移选择、未覆盖的节点以及整个故障域损坏仍可能造成数据缺失。
十、生产环境如何检查 AOF
1. 检查实际配置
text
CONFIG GET appendonly
CONFIG GET appendfsync
CONFIG GET no-appendfsync-on-rewrite
CONFIG GET auto-aof-rewrite-*
不要只看配置文件。启动参数、容器挂载、运行时 CONFIG SET 和托管服务控制面都可能改变最终生效值。
2. 检查持久化状态
text
INFO persistence
重点关注:
aof_enabled:AOF 是否启用。aof_rewrite_in_progress:是否正在重写。aof_rewrite_scheduled:是否等待其他持久化任务结束后重写。aof_last_bgrewrite_status:上次重写是否成功。aof_last_write_status:最近一次 AOF 写入是否成功。aof_current_size与aof_base_size:当前规模与重写基准。aof_pending_bio_fsync:后台队列中待处理的fsync数量。aof_delayed_fsync:被延迟的fsync次数。aof_last_cow_size:上次 AOF 重写产生的 COW 内存规模。
3. 检查延迟来源
先设置非零阈值,再观察:
text
CONFIG SET latency-monitor-threshold 10
LATENCY LATEST
LATENCY DOCTOR
AOF 相关事件包括 aof-write、aof-write-pending-fsync、aof-fsync-always、aof-rename 和 fork。还应结合 iostat、磁盘队列、fsync 延迟、内存余量和交换活动判断。
十一、AOF 上线检查清单与答案
1. 只设置 appendfsync everysec 就已经启用 AOF 了吗?
**没有。**还必须启用 appendonly yes。appendfsync 只是 AOF 启用后的同步策略。
2. 命令返回成功是否一定表示已经落盘?
不一定。 always 会在回复前等待当前批次完成 fsync;everysec 和 no 下,回复时数据可能仍只位于 AOF 缓冲区或操作系统页缓存。
3. everysec 是否绝对最多丢一秒?
**不能把它当作数学上的硬上限。**正常情况下目标是约一秒,但严重 I/O 阻塞、线程调度和系统故障时,实际边界取决于最后一次成功 fsync。
4. AOF 重写是否由后台线程完成?
**不是。**主体由 fork 出的子进程完成。后台线程主要参与 fsync 等任务。
5. Redis 7+ 是否仍只有一个 appendonly.aof 文件?
**不是。**现代 Redis 使用 BASE、一个或多个 INCR,以及 manifest 组成多部件 AOF,并保存在 appenddirname 指定的目录中。
6. 重写期间主线程是否完全不会受影响?
不会完全隔离。 fork、COW、磁盘竞争、内存压力和最终文件切换都可能影响延迟。
7. AOF 能否代替备份和副本?
**不能。**本机磁盘损坏、误操作或整个故障域丢失时,本地 AOF 可能一起失效。AOF、复制、异机备份解决的是不同问题。
8. 备份 Redis 7+ 的 AOF 是否只复制当前 .aof 文件?
**不是。**需要一致地备份 appenddirname 中由 manifest 引用的整组文件。复制期间若恰好发生重写,可能得到不一致的文件组合,因此必须按官方流程暂停自动重写并确认没有重写进行中,或使用能够保证一致性的备份方案。
对于 Redis 7.0--8.8,手工在线复制前通常需要把 auto-aof-rewrite-percentage 暂时设为 0,确认 aof_rewrite_in_progress 为 0,复制完整目录后再恢复原配置。
9. Redis 8.10+ 是否仍要手工暂停重写才能做在线备份?
**不一定。**Redis 8.10 起提供 BACKUP START、BACKUP LIST、BACKUP SEAL、BACKUP CLEANUP 等命令,可以在继续处理写入的同时生成一套自包含、可恢复的 BASE、INCR 与 manifest 文件。备份完成后应复制 BACKUP LIST 返回的密封文件,并在确认远端文件完整后执行清理。
这套命令解决的是一致备份流程,不会自动替你完成异地传输、保留周期、校验和恢复演练。
十二、核心结论
Redis AOF 的完整逻辑可以概括为:
- 成功执行会改变数据集的命令。
- 把可重放的效果追加到 AOF 缓冲区。
- 通过
write(2)写入操作系统页缓存。 - 按
appendfsync策略执行fsync。 - 文件增长后,通过子进程重写基础数据。
- Redis 7+ 用 BASE、INCR 和 manifest 管理多部件 AOF。
- 重启时按 manifest 顺序加载并重建内存数据集。
AOF 的价值不是消灭所有故障,而是提供一个可配置的耐久性边界。真正可靠的方案还需要把 appendfsync、磁盘能力、重写开销、复制、WAITAOF、备份和恢复演练放在同一个设计中考虑。