
Redis 的持久化方案里RDB 和 AOF 是绕不开的两条路。很多人第一次接触 AOF是因为遇到了一件很具体的事机器断电或者进程被强杀之后重启发现数据少了一截而 RDB 的落盘间隔又太长兜不住这段丢失。这时候翻文档看到 AOF说它能更实时地记录写操作于是打开配置就上了。但真跑起来才发现AOF 不是开个开关就完事的东西——刷盘策略选错会拖慢写入重写时机没控好会撑爆磁盘文件损坏了不会修就只能干瞪眼。这篇内容就围绕 AOF 的完整使用链路来讲它到底怎么记录、三种刷盘策略差在哪、重写机制什么时候触发、损坏文件怎么救、以及生产环境里那些文档不会明说但踩过就忘不掉的细节。适合已经会用 Redis 基本命令、但对持久化机制还停留在知道有这回事阶段的开发者也适合正在做数据可靠性方案选型、需要判断 AOF 到底能不能扛住自己业务场景的同学。下面这些内容都是围绕实际配置和运维场景展开的能直接对照着改配置、跑命令。1. AOF 到底在磁盘上写了什么要理解 AOF 的所有行为得先搞清楚它落盘的内容长什么样。这件事想明白了后面刷盘策略、重写、修复这些操作背后的逻辑就都顺了。1.1 从一条 SET 命令看 AOF 的记录格式AOF 全称 Append Only File追加写文件。它的核心思路非常朴素把每一条会修改数据的写命令按执行的先后顺序以 Redis 协议格式追加到文件末尾。注意这里的关键词是写命令——读命令不记录因为读操作不改变数据状态记了也没用。举个具体的例子。客户端执行SET user:1001 张三这条命令进到 AOF 文件里不是原样存成SET user:1001 张三这么一行文本而是被转成 Redis 自己的 RESP 协议格式。实际落盘大概长这样\r\n是协议规定的分隔符*3\r\n$3\r\nSET\r\n$9\r\nuser:1001\r\n$6\r\n张三\r\n拆开看*3表示这条命令有 3 个参数SET、key、value每个参数前面$数字表示该参数的长度后面跟实际内容。这种格式的好处是解析极其简单快速Redis 重启加载时按协议逐条读出来重新执行就行不需要做复杂的语法分析。为什么不用人类可读的纯文本因为 RESP 格式对机器友好加载速度快而且能精确处理二进制安全的内容——比如 value 里带空格、带换行、带特殊字符纯文本格式很容易解析歧义RESP 用长度前缀就完全规避了这个问题。这是设计上的取舍牺牲了一点可读性换来的是加载效率和正确性。1.2 命令追加发生在什么时候这里有个容易混淆的点命令是先写进 AOF 缓冲区还是直接写磁盘答案是先写缓冲区。完整链路是这样的客户端发来写命令Redis 执行命令修改内存数据执行成功后把这条命令按 RESP 格式追加到aof_bufAOF 缓冲区在内存里根据配置的刷盘策略在某个时机把aof_buf的内容写到磁盘文件第 3 步和第 4 步是分开的这个分离设计是理解 AOF 性能特性的关键。命令执行完就返回给客户端了但此时数据可能还在内存缓冲区里没落盘。如果这时候机器断电缓冲区里没刷下去的部分就丢了。丢多少取决于第 4 步的刷盘策略——这就是下一节要展开的内容。注意AOF 记录的是命令执行之后的追加动作但命令本身是先执行再记录的。这意味着如果命令执行到一半进程崩了可能出现内存改了但 AOF 没记的情况。不过 Redis 的单线程模型保证了单条命令的原子性所以不会出现半条命令被记录的问题。1.3 AOF 文件的结构组成一个完整的 AOF 文件不是从头到尾只有命令它由几部分拼起来命令追加区主体部分一条条 RESP 格式的写命令重写产生的头部如果经历过 AOF 重写文件开头可能有一段由重写生成的紧凑命令序列本质还是命令只是把冗余操作合并了可能存在的 RDB 混合格式较新版本支持 AOF 与 RDB 混合持久化重写时前半段用 RDB 格式存快照后半段追加增量命令混合格式这个点值得单独说一句。纯 AOF 重写出来的文件本质还是命令序列加载时要一条条重放数据量大时加载慢。混合格式把重写那一刻的全量数据用 RDB 二进制格式存下来加载时先快速载入 RDB 部分再重放后面的增量命令启动速度能快一个量级。这个特性在配置里通过aof-use-rdb-preamble控制默认在新版本里是开启的。2. 三种刷盘策略可靠性、性能与丢失窗口的三角权衡appendfsync这个配置项是 AOF 使用中最重要的一个决策点它直接决定了断电会丢多少数据和写入性能损耗多大这两件事。三个可选值各有明确的适用场景选错方向要么丢数据要么拖垮性能。2.1 always每条命令都落盘最稳也最慢配置成appendfsync always意思是每追加一条命令到缓冲区后立刻调用 fsync 把缓冲区刷到磁盘刷完才继续处理下一条命令。可靠性上这是天花板理论上最多丢一条正在处理、还没完成 fsync 的命令。但代价很直接——每条写命令都要等一次磁盘 fsync。机械盘上单次 fsync 是毫秒级SSD 上快一些但也不是免费的。这意味着写入吞吐会被 fsync 的延迟死死卡住QPS 上不去。我实测过一个场景同样的硬件always相比everysec纯写入压测的吞吐差了将近一个数量级。所以这个策略只适合那种一条数据都不能丢且写入量本身不大的场景比如某些金融流水记录、关键配置变更。写入量大的业务用它基本等于给自己上刑。2.2 everysec每秒刷一次绝大多数场景的默认答案appendfsync everysec是 Redis 的默认值也是生产环境里用得最多的。它的机制是后台有个专门的刷盘线程或者主线程在时间事件里每隔大约 1 秒把aof_buf里的内容 fsync 到磁盘一次。丢失窗口很明确最坏情况丢 1 秒左右的写命令。对绝大多数业务来说这个窗口是可以接受的——毕竟很多业务本身就有重试和补偿机制丢 1 秒的数据不至于造成灾难。性能上它把 fsync 从每条一次降到每秒一次写入吞吐基本不受 fsync 拖累只在那一秒的刷盘瞬间有极小的抖动。这是可靠性和性能之间最平衡的点也是为什么它被设为默认。有个细节要注意everysec模式下如果上一次 fsync 还没完成主线程不会傻等而是继续往缓冲区写。如果磁盘特别慢fsync 积压Redis 会在主线程里做阻塞式 fsync 来兜底这时候会出现明显的延迟毛刺。所以用everysec也要关注磁盘的 fsync 能力别用那种 IO 抖动严重的盘。2.3 no交给操作系统性能最好但丢失窗口不可控appendfsync no的意思是 Redis 自己不主动 fsync只把数据写到操作系统的页缓存page cache什么时候真正落盘由操作系统决定通常 Linux 是 30 秒左右刷一次脏页。这个策略下 Redis 的写入几乎不受磁盘影响性能最好。但丢失窗口完全不可控——操作系统崩了或者断电最多可能丢 30 秒的数据。而且这个 30 秒是系统行为Redis 管不了。它适合什么场景数据能从别处重建、或者对丢失不敏感的缓存类数据。如果你的 Redis 纯粹当缓存用丢了从数据库回源就行那no是可以考虑的。但只要数据有唯一真相的属性就别碰这个选项。三种策略的对比整理成表更直观策略丢失窗口写入性能适用场景always最多 1 条命令最差受 fsync 延迟限制极少数不能丢的数据写入量小everysec约 1 秒好接近无持久化绝大多数生产场景的默认选择no不可控可能数十秒最好纯缓存、可重建数据2.4 怎么根据业务选一个判断框架选策略不要拍脑袋按两个维度问自己数据丢了能不能恢复写入量有多大。数据能从上游重建缓存场景选no或everysec都行看你对性能的渴求程度。数据是唯一来源、丢了就是事故那必须everysec起步写入量小且对丢失零容忍才考虑always。写入量巨大又要求零丢失那 Redis 单机 AOF 可能不是最优解得考虑主从加多副本、或者换更适合的存储方案。实际配置就一行在redis.conf里appendonly yes appendfsync everysec改完重启或者用CONFIG SET appendfsync everysec动态生效注意appendonly的动态开启在部分版本行为有差异稳妥起见改配置文件重启。3. AOF 重写为什么文件会越写越大以及怎么收住AOF 是追加写这个特性决定了文件只会变大不会变小。一条 key 被反复 SET 一百次AOF 里就有一百条 SET 记录但实际有效数据只有最后一次。文件膨胀到几个 G 甚至几十 G不仅占磁盘重启加载时重放这些冗余命令也慢得离谱。AOF 重写就是来解决这个问题的。3.1 重写的本质用当前数据状态生成最小命令集重写不是去读旧 AOF 文件做压缩而是直接读当前内存里的数据为每个 key 生成一条能重建它当前状态的最简命令。比如一个 list 经过多次 RPUSH 变成了 100 个元素重写时不会保留 100 条 RPUSH而是生成一条RPUSH key v1 v2 ... v100或者分批次把当前状态一次性表达出来。这样重写出来的新文件体积通常比旧文件小得多而且只包含重建当前状态所必需的命令没有任何历史冗余。加载时重放这些命令得到的就是重写那一刻的数据快照。3.2 触发时机两个参数怎么配合重写不是随时都在跑它由两个配置项控制触发条件auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb含义是当 AOF 文件大小比上一次重写后的基准大小增长了 100%也就是翻倍并且文件绝对大小超过 64MB 时才触发自动重写。两个条件是与的关系缺一不可。为什么要两个条件min-size防止小文件频繁重写——一个才几 MB 的文件翻倍也就十几 MB重写一次的开销可能比收益还大。percentage则控制增长比例避免文件无限膨胀。这两个值需要根据业务写入量调写入特别猛的业务percentage可以调低到 50 甚至更低让重写更早介入写入平缓的保持默认或调高都行。手动触发用命令BGREWRITEAOF这个命令是后台异步执行的不会阻塞主线程处理请求。3.3 重写期间的新写入去哪了AOF 重写缓冲区这是重写机制里最精妙的设计也是面试常问、实际排错常踩的点。重写是后台子进程干的活它读的是重写开始那一刻的内存快照。但重写过程中主线程还在处理新的写命令这些新命令如果只写进旧 AOF 文件那重写完成后新文件里就缺了这段时间的数据。Redis 的解法是引入AOF 重写缓冲区aof_rewrite_buf。重写期间新的写命令同时追加到旧 AOF 文件和重写缓冲区子进程把内存快照写成新的 AOF 文件写到临时文件子进程写完后主线程把重写缓冲区里的内容追加到新文件末尾用新文件原子替换旧文件这样新文件 重写时刻的全量快照 重写期间的所有增量数据完整。这个设计保证了重写不丢数据代价是重写期间内存里要多维护一份重写缓冲区写入量大的时候这块内存会涨需要留意。提示重写期间如果写入量特别大重写缓冲区可能撑大内存。可以观察INFO persistence里的aof_rewrite_buffer_length相关指标异常增长说明重写跟不上写入速度考虑调大重写触发阈值或错峰执行。3.4 重写带来的阻塞风险与规避虽然重写是子进程异步做的但有两个环节可能影响主线程一是fork 子进程的瞬间。fork 本身要复制页表内存越大耗时越长这个瞬间主线程是阻塞的。几十 G 的实例 fork 一次可能阻塞几百毫秒甚至更久表现为请求延迟毛刺。规避办法是控制单实例内存不要太大或者用主从架构在从库上做重写。二是重写缓冲区追加到新文件的最后阶段。这个阶段主线程要把缓冲区内容写进新文件如果缓冲区很大这一步也会阻塞。所以重写期间控制写入量、避免缓冲区暴涨很重要。实际运维里我一般会把自动重写的触发阈值调得比默认激进一点让每次重写的数据量小一些、频率高一些单次 fork 和收尾的阻塞就小。用小步快跑换平稳比攒一个巨大文件一次重写要安全。4. AOF 文件损坏了怎么救从报错到恢复的完整链路AOF 最让人头疼的场景之一是重启时加载失败日志里甩出一句Bad file format reading the append only file。这时候别慌先搞清楚损坏发生在哪、能救回多少再决定怎么处理。4.1 先判断是尾部截断还是中间损坏AOF 是追加写最常见的损坏是尾部不完整——比如写到最后一条命令时机器断电文件末尾留了半条命令。这种损坏相对好处理因为前面的数据都是完整的。判断方法用redis-check-aof工具检查不带--fixredis-check-aof /path/to/appendonly.aof它会扫描文件报告是否能正常解析、以及如果发现问题出在哪个位置。如果报的是文件末尾有截断或不完整命令那就是尾部损坏可以修。如果报的是中间某处格式错误那可能是更严重的问题比如磁盘坏块修复要谨慎因为中间损坏意味着损坏点之后的数据可能也不可信。4.2 用 redis-check-aof --fix 修复尾部截断确认是尾部截断后用修复模式redis-check-aof --fix /path/to/appendonly.aof这个命令会扫描文件找到第一条不完整的命令把从那里到文件末尾的内容全部截掉只保留前面完整可解析的部分。执行时会提示你确认输入y继续。修复的本质是丢弃不完整的尾巴保住完整的前半段。所以修复后你会丢掉最后那部分没写完的数据但前面的数据是安全的。修复前务必备份原文件cp appendonly.aof appendonly.aof.bak这个备份动作千万别省。修复工具虽然可靠但万一操作失误或者文件情况特殊有备份才有退路。4.3 混合持久化格式下的修复注意点如果开了aof-use-rdb-preamble混合格式AOF 文件前半段是 RDB 二进制、后半段是命令。这种情况下redis-check-aof的修复逻辑依然适用但要注意RDB 部分如果损坏修复工具处理起来更棘手因为它不是命令序列没法简单截断。混合格式下如果 RDB 头部损坏通常意味着重写过程出了问题这种情况建议直接从备份恢复或者用 RDB 快照重建别硬修。所以开了混合持久化备份策略要跟上不能只依赖 AOF 自身的修复能力。4.4 修复后验证别修完就直接上生产修复完不是终点得验证数据完整性。稳妥的做法是把修复后的文件放到一个测试实例上加载确认能正常启动抽查关键 key 是否存在、值是否正确对比修复前后的数据量用DBSIZE或抽样确认丢失范围符合预期应该只丢尾部那部分确认无误后再替换生产文件。直接拿修复文件上生产万一还有隐藏问题二次故障的排查成本更高。5. 生产环境里那些配置文档不会写的细节前面讲的都是机制和标准操作这一节聊点实际运维中积累的经验都是文档里不会明说、但踩过就记住的东西。5.1 appendfsync everysec 下的延迟毛刺从哪来用everysec的时候偶尔会看到请求延迟突然飙一下几十毫秒甚至上百毫秒。很多人第一反应是网络或者慢查询其实很可能是 AOF 的 fsync 在捣鬼。前面提过everysec下如果上一次 fsync 还没完成主线程会阻塞等待。磁盘 IO 抖动比如同机器上别的进程在狂写、或者云盘的 IO 配额被打满会导致 fsync 变慢进而拖住主线程。排查这类毛刺要看INFO persistence里的aof_delayed_fsync计数——这个值持续增长说明 fsync 延迟已经在影响主线程了。应对办法把 AOF 文件放到独立的、IO 能力有保障的盘上别和 RDB、日志、其他高 IO 进程抢盘。云环境下关注磁盘的 IOPS 和吞吐配额别让 AOF 的 fsync 排在别的 IO 后面。5.2 AOF 和 RDB 要不要同时开这个问题没有标准答案取决于你要什么。两个都开重启时 Redis 优先用 AOF 恢复因为 AOF 通常更完整RDB 作为额外备份。代价是两份持久化开销磁盘占用翻倍fork 也更频繁。我的经验是以 AOF 为主做数据可靠性RDB 保留但降低频率做冷备份。比如 AOF 用everysec保证实时性RDB 配置成几小时一次甚至一天一次纯粹作为万一 AOF 彻底坏了的兜底。这样既保证了恢复点目标又不会让 RDB 的 fork 频繁干扰主线程。如果业务对恢复速度要求极高、数据量又大纯 AOF 加载慢的问题可以用混合持久化缓解不必为了加载速度强行依赖 RDB。5.3 磁盘空间规划别等写满了才想起来AOF 文件会持续增长重写时还要额外空间写临时文件新文件写完才替换旧的所以峰值需要约两倍空间。磁盘规划要按当前 AOF 大小 × 2 余量来算别卡着实际大小配盘。另外要监控磁盘使用率设个阈值告警比如 80%。AOF 写满磁盘的后果很严重Redis 会拒绝所有写命令取决于stop-writes-on-bgsave-error相关配置和具体版本行为业务直接受影响。提前告警比事后救火强得多。5.4 重写频率和实例内存大小的关系前面提过 fork 阻塞和内存大小正相关。单实例内存越大fork 越慢重写期间的重写缓冲区也越大。所以大内存实例要更谨慎地控制重写频率或者干脆用主从架构把重写放到从库上做主库只负责写。一个实用的经验值单实例内存控制在十几 G 以内fork 阻塞通常在可接受范围超过这个量级就要认真评估 fork 对延迟的影响必要时拆分实例或调整架构。5.5 监控指标清单AOF 相关的监控除了常规的内存、QPS重点盯这几个指标含义异常信号aof_current_size当前 AOF 文件大小持续增长不回落说明重写没生效aof_base_size上次重写后的基准大小与 current 比值接近触发阈值要留意aof_delayed_fsyncfsync 延迟次数持续增长说明磁盘 IO 跟不上aof_rewrite_in_progress是否正在重写长期为 1 说明重写卡住aof_last_bgrewrite_status上次重写结果为 err 要立即排查这些指标通过INFO persistence都能拿到接到监控系统里设好阈值比出事后翻日志高效得多。6. 一个完整的 AOF 配置落地示例把前面的内容串起来给一份可以直接参考的配置和操作流程。假设场景是单实例 Redis数据是业务主数据不能丢写入量中等机器是 SSD。配置文件关键项# 开启 AOF appendonly yes # 刷盘策略兼顾可靠性和性能 appendfsync everysec # 混合持久化加快重启加载 aof-use-rdb-preamble yes # 重写触发增长 100% 且超过 64MB auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 重写期间不阻塞 fsync视版本和场景调整 no-appendfsync-on-rewrite nono-appendfsync-on-rewrite这个参数单独说一下。设成yes时重写期间主线程不做 fsync把 fsync 交给操作系统能减少重写期间的延迟抖动但代价是重写期间丢失窗口变大。设成no则重写期间照常 fsync可靠性优先。数据不能丢的场景建议no能容忍重写期间多丢一点的可以设yes换平稳。上线后的操作流程改配置重启实例或按版本支持情况动态开启 AOF用INFO persistence确认aof_enabled:1观察aof_current_size开始增长压测写入观察aof_delayed_fsync是否增长、延迟是否有毛刺手动执行一次BGREWRITEAOF确认重写能正常完成、aof_last_bgrewrite_status:ok模拟一次重启确认能从 AOF 正常加载、数据完整接入监控把 5.5 节的指标配上告警这套流程走一遍AOF 的基本可靠性就有底了。剩下的就是根据实际运行数据微调重写阈值和刷盘策略。最后分享一个我自己的习惯每次调整 AOF 相关配置前先在测试实例上用接近生产的数据量和写入压力跑一遍重点看aof_delayed_fsync和重写耗时。AOF 的很多问题不是配置错了而是配置和实际负载不匹配——同样的everysec在 IO 充裕的盘上稳如老狗在 IO 紧张的盘上就是延迟毛刺的来源。配置是死的负载是活的拿真实压力验证过再上比照搬任何最佳实践都靠谱。