
1. 从一次数据丢失事故说起AOF 到底解决了什么问题Redis 作为内存数据库数据全在内存里进程一挂、机器一断电内存里的东西就全没了。我最早接触 Redis 的时候团队里有人觉得“反正是缓存丢了就丢了”结果有一次线上一个订单状态机把 Redis 当成了唯一存储机器重启后订单状态全部回退排查了大半天才定位到是持久化没配好。那次事故之后我把 Redis 的持久化机制从头到尾捋了一遍AOF 就是绕不开的核心。AOF 全称 Append Only File中文一般叫“追加写文件”。它的思路非常朴素Redis 每执行一条会修改数据的写命令就把这条命令按 Redis 协议格式追加到文件末尾。重启的时候Redis 把这个文件从头到尾读一遍把里面的命令重新执行一遍数据就恢复了。你可以把它理解成数据库领域的“操作日志重放”跟很多关系型数据库的 redo log 在思路上有相似之处但 Redis 的 AOF 记录的是命令本身而不是物理页的变更。这个机制能做什么最直接的就是在 Redis 进程崩溃或机器宕机后把数据恢复到接近崩溃前的状态。相比 RDB 快照那种“隔一段时间拍一张照片”的方式AOF 记录的是“每一笔改动”理论上数据丢失的窗口可以压缩到 1 秒甚至更短。适合谁来参考我觉得三类人最需要一是把 Redis 用在有一定数据可靠性要求场景的后端开发二是负责 Redis 运维、需要做备份和故障恢复的运维同学三是正在准备面试、想把持久化机制讲清楚的同学。哪怕你只是把 Redis 当纯缓存用了解 AOF 也能帮你在“要不要开”“怎么开”上做出有依据的判断而不是照抄一份配置文件了事。后面我会围绕 AOF 的工作流程、三种刷盘策略的取舍、AOF 重写的触发与实现、配置参数怎么算、故障怎么排查这几个维度展开尽量把“为什么这么设计”讲透而不是只丢一堆配置项给你。2. AOF 的整体设计与核心思路拆解2.1 为什么是“追加写”而不是“随机改”理解 AOF 的第一个关键点是它为什么选择“追加”这种看起来有点“笨”的方式。如果每次修改都去文件里找到对应位置改掉那就需要维护数据在文件中的偏移量映射文件结构会变得复杂而且随机写在小文件上还行文件一大性能就崩。追加写的好处是写入永远是顺序的顺序 I/O 在绝大多数存储介质上都远快于随机 I/O而且不需要维护复杂的索引结构实现简单、出错概率低。另一个隐含的好处是崩溃安全性。追加写意味着已经写进去的历史内容不会被覆盖即使写到一半进程挂了最坏情况是文件末尾多了一条不完整的命令。Redis 在加载 AOF 时对这种情况有容错处理可以截断最后一条不完整的命令继续加载而不是整个文件报废。这个设计取舍很务实用“可能丢最后一条”换“前面所有数据都安全”。2.2 AOF 的完整生命周期从命令到恢复一条写命令从进入 Redis 到最终落盘、再到重启恢复大致经过这么几个环节。客户端发来写命令Redis 执行命令、修改内存数据同时把这条命令按照 RESP 协议追加到aof_buf这个内存缓冲区里。注意这里是先写内存缓冲区不是直接写磁盘。然后根据配置的刷盘策略在事件循环的适当时机把缓冲区内容写到磁盘文件。文件写到一定程度超过配置的阈值触发 AOF 重写把冗余命令合并压缩。重启时如果开启了 AOFRedis 优先加载 AOF 文件而不是 RDB 文件逐条重放命令恢复数据。这个流程里有两个容易被忽略的点。第一命令是先执行再记录也就是说 AOF 记录的是“已经执行成功的命令”不是“准备执行的命令”所以不会出现重放时命令执行失败导致数据不一致的问题前提是命令本身是确定性的。第二写缓冲区和刷盘是两回事很多人把appendfsync理解成“写文件”其实它控制的是“从内存缓冲区刷到磁盘”这个动作写缓冲区本身几乎不耗时真正决定数据安全性和性能的是刷盘策略。2.3 AOF 与 RDB 的定位差异经常有人问“AOF 和 RDB 到底选哪个”。我的看法是它们解决的不是同一个问题。RDB 是快照适合做冷备份、全量迁移、快速重启恢复文件紧凑、加载快但两次快照之间的数据会丢。AOF 是增量日志适合对数据丢失敏感的场景恢复粒度细但文件更大、加载更慢。生产环境里我一般建议两个都开RDB 用于定期全量备份和快速恢复的兜底AOF 用于把数据丢失窗口压到最小。Redis 4.0 之后引入的混合持久化aof-use-rdb-preamble更是把两者结合了起来重写时前半段用 RDB 格式存全量快照后半段追加增量命令兼顾了加载速度和数据完整性。提示不要因为开了 AOF 就关掉 RDB。AOF 文件在极端情况下比如磁盘写满、文件损坏可能不可用这时候 RDB 快照就是最后的救命稻草。3. 三种刷盘策略的取舍性能与安全的平衡术3.1 appendfsync 的三个取值到底差在哪appendfsync是 AOF 最核心的配置项它有三个取值always、everysec、no。这三个值的差异本质上是“什么时候把内存缓冲区里的命令真正写到磁盘”的差异。always表示每一条写命令执行完都立刻把缓冲区刷到磁盘并调用 fsync 确保落盘。数据安全性最高理论上一条都不丢但性能最差因为每条命令都要经历一次磁盘同步磁盘的 IOPS 直接成为瓶颈。everysec表示每秒刷一次盘是默认值也是绝大多数场景的推荐值。它把数据丢失窗口控制在 1 秒左右性能损耗可以接受。no表示不主动刷盘交给操作系统决定什么时候把页缓存写回磁盘性能最好但一旦进程或机器崩溃丢失的数据量不可控可能几秒也可能几十秒。我做过一个粗略的对比测试在同样的硬件和压测条件下always的写入吞吐大概只有everysec的三分之一到二分之一具体数字跟磁盘类型强相关。如果是机械盘差距更明显如果是带掉电保护的 SSDalways的代价会小一些但仍然不建议无脑上always。策略数据丢失窗口性能影响适用场景always几乎为 0最大每条命令同步落盘金融级、不可丢数据的极少数场景everysec约 1 秒较小每秒一次同步绝大多数生产环境的默认选择no不可控取决于系统最小几乎不主动同步纯缓存、可容忍大量丢失的场景3.2 为什么 everysec 是“甜点”everysec之所以成为默认值是因为它在安全性和性能之间找到了一个工程上很舒服的平衡点。Redis 用一个后台线程每秒执行一次 fsync主线程只管往缓冲区写不阻塞命令处理。即使 fsync 因为磁盘慢而耗时较长主线程也不会被卡住这一点在后面的问题排查里还会提到。但everysec不是没有代价。它意味着最坏情况下丢 1 秒的数据。如果你的业务对 1 秒的丢失都无法接受那要么上always要么在应用层做补偿比如写 Redis 的同时写一份消息队列或数据库。我个人的经验是绝大多数业务对 1 秒的丢失是能接受的真正不能接受的场景往往也不该只靠 Redis 来保证数据可靠性。3.3 选型时容易踩的坑第一个坑是盲目上 always。有人觉得“数据最重要那就 always 呗”结果上线后 QPS 掉了一大截又回头改配置。选型要看业务真实需求不是越安全越好。第二个坑是以为 everysec 绝对只丢 1 秒。实际上如果磁盘特别慢fsync 阻塞超过 1 秒Redis 主线程在写缓冲区时可能会被阻塞取决于版本和具体实现这时候延迟会抖动。所以everysec的前提是磁盘不能太拉胯。第三个坑是忽略操作系统的页缓存。即使配置了everysec数据先写到操作系统页缓存再由 fsync 落盘。如果机器突然断电页缓存里还没落盘的数据就丢了。这也是为什么有些团队会给 Redis 所在机器配带掉电保护的存储设备。注意appendfsync no并不等于“不写磁盘”只是不主动调用 fsync数据仍然会进操作系统页缓存最终由内核刷盘。它的风险在于刷盘时机不可控。4. AOF 重写文件膨胀的治理方案4.1 为什么 AOF 文件会越来越大AOF 记录的是每一条写命令这意味着同一个 key 被反复修改文件里就会堆积大量历史命令。比如一个计数器被INCR了一万次AOF 里就有一万条INCR命令但恢复数据其实只需要最后的值。再比如一个 list 被反复LPUSH和LPOP很多中间状态都是冗余的。文件无限增长会带来两个问题磁盘占用越来越大重启加载越来越慢。AOF 重写rewrite就是来解决这个问题的。它的思路不是“读旧文件做压缩”而是直接根据当前内存中的数据生成一份最小化的命令集合。比如那个被 INCR 一万次的计数器重写后只需要一条SET key 10000。这样生成的新文件体积会大幅缩小加载也更快。4.2 重写是怎么做到不阻塞主线程的重写的过程比较巧妙。Redis 会 fork 出一个子进程子进程持有 fork 那一刻的内存数据快照然后遍历数据、生成新 AOF 文件。主进程 meanwhile 继续处理客户端请求不受影响。但这里有个问题子进程生成新文件的过程中主进程还在接收新的写命令这些新命令不能丢。Redis 的解决办法是主进程在重写期间把新的写命令同时追加到旧的 AOF 文件和一块重写缓冲区aof_rewrite_buf里。子进程写完新文件后主进程把重写缓冲区里的增量命令追加到新文件末尾然后用新文件原子替换旧文件。这样新文件既包含了 fork 时刻的全量数据又包含了重写期间的所有增量修改数据不丢。这个设计的关键在于fork 的写时复制COW机制。fork 本身很快但子进程和父进程共享内存页父进程一旦修改某个页内核就会复制一份产生额外的内存开销。所以重写期间如果写入非常频繁内存占用可能会上涨。这也是为什么重写不是越频繁越好。4.3 触发时机与参数计算AOF 重写有两种触发方式手动和自动。手动就是执行BGREWRITEAOF命令。自动触发靠两个参数auto-aof-rewrite-percentage和auto-aof-rewrite-min-size。auto-aof-rewrite-min-size是文件大小的下限默认 64MB意思是文件小于这个值就不触发重写避免小文件频繁重写。auto-aof-rewrite-percentage是增长比例默认 100意思是当前文件大小比上次重写后的基础大小增长了 100%也就是翻倍就触发重写。举个例子假设上次重写后文件是 100MBmin-size是 64MBpercentage是 100。那么当文件增长到 200MB 时触发重写。如果上次重写后是 40MB虽然翻倍到 80MB 已经超过 40MB 的两倍但 80MB 大于 64MB 的 min-size所以也会触发。如果上次重写后是 20MB翻倍到 40MB虽然比例到了但 40MB 小于 64MB不触发。这两个参数怎么调我的经验是写入量大的实例适当调低 percentage 或 min-size让重写更早发生避免单次重写耗时过长写入量小的实例可以调高减少重写频率。但要注意重写本身消耗 CPU 和内存COW太频繁反而影响性能。一般保持默认值就能应付大多数场景除非你观察到文件增长异常快或者重写耗时明显影响服务。5. 实操配置与完整落地步骤5.1 基础配置项逐条说明下面是一份我常用的 AOF 基础配置逐条解释每个参数的作用和取值考量。# 开启 AOF默认是 no appendonly yes # AOF 文件名默认 appendonly.aof appendfilename appendonly.aof # 刷盘策略生产环境推荐 everysec appendfsync everysec # 重写期间是否禁止 fsync默认 no no-appendfsync-on-rewrite no # 自动重写触发比例 auto-aof-rewrite-percentage 100 # 自动重写触发的最小文件大小 auto-aof-rewrite-min-size 64mb # 加载时是否忽略最后一条可能不完整的命令默认 yes aof-load-truncated yes # 是否开启混合持久化Redis 4.0 aof-use-rdb-preamble yesno-appendfsync-on-rewrite这个参数值得单独说。它控制的是重写期间是否暂停 fsync。默认no表示不暂停重写期间仍然正常刷盘数据安全性有保障但如果磁盘压力大fsync 可能阻塞主线程导致延迟抖动。设为yes则重写期间不 fsync性能更平滑但重写期间的数据丢失风险会上升因为不刷盘。我的建议是磁盘性能好就保持 no磁盘是瓶颈且能容忍重写期间的风险再考虑 yes。aof-load-truncated yes是容错开关。如果 AOF 文件末尾因为崩溃而写了半条命令设为 yes 时 Redis 会打印警告并截断最后一条不完整命令继续加载设为 no 则直接拒绝启动。生产环境建议保持 yes否则一次意外崩溃可能导致服务起不来。5.2 从零开启 AOF 的完整流程假设你有一个已经在跑的 Redis 实例之前没开 AOF现在要开启。直接改配置文件重启是有风险的因为重启后 Redis 会优先加载 AOF 文件而 AOF 文件还不存在或为空可能导致数据“看起来丢了”。正确的做法是先通过CONFIG SET appendonly yes在线开启 AOF。Redis 会在后台触发一次重写根据当前内存数据生成 AOF 文件。观察日志确认 AOF 重写完成、文件已生成。修改配置文件把appendonly yes写进去保证下次重启后配置仍然生效。用INFO persistence检查aof_enabled是否为 1aof_rewrite_in_progress是否为 0。这个顺序很重要。如果直接改配置文件重启Redis 启动时发现appendonly yes但 AOF 文件不存在会创建一个空文件并加载内存里的数据就没了除非同时有 RDB 且配置了加载。在线开启则是由当前数据生成 AOF不会丢。5.3 验证 AOF 是否真正生效配置改完不代表万事大吉得验证。我一般做三步检查。第一步INFO persistence看aof_enabled:1、aof_last_bgrewrite_status:ok、aof_last_write_status:ok。第二步往 Redis 写几个 key然后去 AOF 文件里tail一下看命令有没有按预期追加进去。第三步做一次恢复演练停掉 Redis备份当前 AOF 文件删掉几个 key 再重启看数据能不能从 AOF 恢复回来。第三步最能说明问题但要在测试环境做别在生产上玩。提示AOF 文件是文本格式混合持久化下前半段是二进制 RDB可以直接用cat或tail查看调试时很方便。但不要手动编辑它格式错了会导致加载失败。6. 常见问题与排查技巧实录6.1 AOF 文件异常膨胀怎么查文件膨胀是最常见的问题。先看INFO persistence里的aof_current_size和aof_base_size算一下增长比例。如果比例早就超过auto-aof-rewrite-percentage却没触发重写检查aof_rewrite_in_progress是不是一直为 1重写卡住了或者aof_last_bgrewrite_status是不是err上次重写失败。重写失败常见原因是磁盘空间不足或 fork 失败内存不够。如果重写正常但文件还是大那可能是业务本身写入量就大或者存在大量“写了又删”的无效操作。这时候可以考虑调低auto-aof-rewrite-percentage让重写更积极或者从业务层面减少无意义的写。6.2 重启加载 AOF 特别慢怎么办加载慢通常有两个原因文件太大或者命令太多。文件大就靠重写压缩命令多则要考虑是不是有大量细碎命令比如逐条 SET 而不是批量。另外如果开启了混合持久化加载时前半段 RDB 是二进制解析速度比纯命令重放快很多所以强烈建议开启aof-use-rdb-preamble yes这是提升加载速度最直接的手段。还有一个容易被忽略的点加载 AOF 是单线程逐条执行的期间 Redis 不对外服务。如果文件有几个 GB加载几分钟很正常。对于超大实例可以考虑用 RDB 做主恢复手段AOF 只作为补充。6.3 磁盘写满导致 AOF 写入失败这是生产环境的经典故障。INFO persistence里aof_last_write_status变成err日志里会有 “Cant write to the AOF file” 之类的报错。Redis 在写入失败后的行为取决于版本和配置较新版本会拒绝新的写命令返回错误保护数据一致性。处理办法是尽快清理磁盘空间然后执行BGREWRITEAOF重新生成文件或者用CONFIG SET appendonly no临时关闭再开启。预防手段是给 Redis 所在磁盘设置监控告警留足余量。另外maxmemory配置不当也可能间接导致问题——如果内存满了触发大量淘汰AOF 里会记录大量 DEL 命令文件增长反而更快。6.4 常见问题速查表现象可能原因排查与处理文件持续膨胀不重写重写卡住或失败查aof_rewrite_in_progress、aof_last_bgrewrite_status检查磁盘和内存重启加载极慢文件大、命令多、未开混合持久化开启aof-use-rdb-preamble调低重写阈值写入报错磁盘满、权限问题查aof_last_write_status清理磁盘检查文件权限数据丢失超出预期刷盘策略为 no 或 everysec 且磁盘慢评估是否改 always或应用层补偿重写期间延迟抖动fsync 阻塞主线程评估no-appendfsync-on-rewrite升级磁盘启动报 AOF 格式错误文件损坏、手动编辑过用redis-check-aof --fix修复或从备份恢复redis-check-aof这个工具值得记住它是 Redis 自带的 AOF 检查修复工具--fix参数可以截断末尾不完整的命令。但修复前一定要备份原文件修复本身也可能丢数据。6.5 几个踩坑心得第一个心得AOF 不是万能的备份方案。它防的是进程崩溃和机器宕机防不了误操作比如FLUSHALL也会被记录进 AOF重放时照样清空。所以定期 RDB 备份 异地存储仍然必要。第二个心得重写期间监控内存。fork 的 COW 机制在写入频繁时会导致内存翻倍如果机器内存本来就紧张重写可能触发 OOM。我遇到过重写时内存暴涨导致被系统 kill 的情况后来给重写加了内存余量监控才稳住。第三个心得不要频繁手动 BGREWRITEAOF。有人看到文件大了就手动触发一次结果重写本身消耗资源频繁触发反而拖慢服务。让自动重写按阈值工作就好手动触发留给特殊场景比如刚做完大批量删除想立刻压缩文件。7. 混合持久化与后续演进方向7.1 混合持久化为什么值得开Redis 4.0 引入的aof-use-rdb-preamble是我认为最值得开启的持久化优化之一。它的原理是AOF 重写时子进程不再生成纯命令格式的新文件而是前半段用 RDB 二进制格式存当前数据的全量快照后半段追加重写期间的增量命令。加载时先快速加载 RDB 部分再重放增量命令。这个设计的好处很直接加载速度大幅提升RDB 解析远快于逐条命令重放文件体积也更小。代价是 AOF 文件不再是纯文本可读性下降调试时不能直接cat看全貌了。但权衡下来生产环境我基本都开。需要注意的是开启后如果要用redis-check-aof修复工具对混合格式的支持要看版本老版本可能处理不了升级前要确认。7.2 多部分 AOF 与后续版本的变化Redis 7.0 对 AOF 做了较大改动引入了multi-part AOF机制。简单说就是把 AOF 拆成基础文件base和增量文件incr放在一个目录里管理重写时生成新的 base 文件增量文件滚动切换。这个改动解决的是老版本单文件重写时的一些边界问题比如重写期间的文件替换原子性、增量缓冲的管理等。如果你用的是 7.0 及以上版本AOF 目录下会看到appendonly.aof.1.base.rdb、appendonly.aof.1.incr.aof这类文件这是正常现象不要手动去动它们。版本差异带来的实操影响是升级大版本前一定要在测试环境验证 AOF 的加载和重写行为。我见过升级后因为 AOF 目录结构变化导致启动失败的案例根因是旧配置和新机制的兼容问题。升级前备份 AOF 文件、升级后做一次恢复演练是稳妥的做法。7.3 持久化策略的最终建议绕了一圈回到最实际的问题到底怎么配。我的建议是按场景分档。纯缓存场景可以只开 RDB 或者干脆不开持久化丢了从源头重建一般业务场景RDB AOF 都开appendfsync everysec开混合持久化这是性价比最高的组合数据敏感场景在上一档基础上考虑appendfsync always或应用层双写同时做好磁盘冗余和监控。无论哪一档定期做恢复演练都是必须的配置写得再漂亮没验证过恢复流程等于没配。我个人在实际操作中的体会是AOF 的坑大多不在配置本身而在“以为配了就万事大吉”的心态。文件膨胀、磁盘写满、重写卡住、加载超时这些问题平时不显山露水真出事的时候才暴露。所以与其纠结参数调到多精细不如先把监控和演练做起来让问题在可控范围内被发现。最后再分享一个小技巧把INFO persistence的关键指标aof_current_size、aof_last_bgrewrite_status、aof_last_write_status接入监控面板设好阈值告警比事后翻日志高效得多。