6.2 AOF日志:先记命令再执行 本节摘要:AOF(追加式日志文件)记录每条写命令的 RESP 文本,恢复时逐条重放。丢数据的窗口由 appendfsync 策略决定:always 最多丢一条、everysec 最多丢一秒、no 听天由命。日志会膨胀,重写机制按"当前内存结果"生成最小命令集来减肥。 与 RDB 相反的哲学 RDB 记"某一刻的结果",AOF 记"全部的历史过程"。写命令到达后先追加进 AOF 缓冲区,再执行、再返回客户端——顺序与多数数据库的 WAL 相反(Redis 是先执行后落盘的设计也讨论过,最终选择协议文本直录,可读且可人工修复)。
本节摘要:AOF(追加式日志文件)记录每条写命令的 RESP 文本,恢复时逐条重放。丢数据的窗口由 appendfsync 策略决定:always 最多丢一条、everysec 最多丢一秒、no 听天由命。日志会膨胀,重写机制按"当前内存结果"生成最小命令集来减肥。
RDB 记"某一刻的结果",AOF 记"全部的历史过程"。写命令到达后先追加进 AOF 缓冲区,再执行、再返回客户端——顺序与多数数据库的 WAL 相反(Redis 是先执行后落盘的设计也讨论过,最终选择协议文本直录,可读且可人工修复)。
appendonly yes appendfsync everysec # 三选一:always / everysec / no auto-aof-rewrite-percentage 100 # 比上次重写后体积翻倍才触发 auto-aof-rewrite-min-size 64mb # 且不小于64MB
| 策略 | 行为 | 丢数据窗口 | 吞吐影响 |
|---|---|---|---|
| always | 每条命令都 fsync | 最多丢刚到的 1 条 | 磁盘 IO 成主角,吞吐显著下降 |
| everysec | 每秒一次后台线程 fsync | 最多丢 1 秒 | 推荐默认,性能与安全的平衡点 |
| no | 交给操作系统,约30秒 | 不确定,可能几秒 | 最快,风险自担 |
三种策略的现场判别有个小窍门:写入压测时盯两个数——吞吐曲线与 INFO persistence 的 aof_delayed_fsync。everysec 下这个计数持续增长,说明磁盘已经跟不上一秒一批的节奏,服务端在反复"宽限两秒再刷",写延迟随之抬升;这时换更快的盘比调策略更治本。always 适合的场景很窄:写极低频但条条不能丢(配置类、指标类的小实例),拿吞吐换安心。
机械盘时代 always 几乎不可用;固态盘上 always 可行但仍贵。everysec 是绝大多数实例的答案:一秒的窗口对多数业务可接受,性能损失个位数百分比。
"日志是文本"不是一句修辞,打开文件看一眼就懂。对同一个键执行两步操作后,AOF 文件里躺着的就是两段 RESP:
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$2\r\nok\r\n *2\r\n$4\r\nINCR\r\n$4\r\nname\r\n
每行一个命令、参数带长度前缀,与 1.3 的协议完全同构——所以 AOF 既是持久化文件,也是"可人工审计的命令流水"。应急场景里它能救命:怀疑某条命令把数据写坏时,直接在文件里全文检索键名,前后命令一目了然;要从"AOF 里恢复单个键"时,把含该键的行抽出来重放到新实例即可。这种可读性是二进制 RDB 永远给不了的。
日志按命令累积:一个计数器 INCR 十万次,AOF 里躺着十万行,内存里只是一个整数。BGREWRITEAOF 的思路与 RDB 殊途同归——fork 子进程,按当前内存的实际内容生成等价的最小命令集写入新文件,期间新写入追加进重写缓冲,最后把补丁缝进新文件,原子替换旧文件。
> BGREWRITEAOF Background append only file rewriting started > INFO persistence # 关注 aof_current_size 与 aof_base_size
INFO persistence 里属于 AOF 的几项值得逐个认识:aof_current_size 是当前文件字节数,aof_base_size 是上次重写后的基础大小,两者相除能估出"距离下次自动重写还有多远";aof_rewrite_in_progress 与 aof_last_bgrewrite_status 分别盯进度与健康度;最要紧的是 aof_pending_bio_fsync 与 aof_delayed_fsync——后者大于零说明磁盘忙到没能在两秒内完成 fsync,服务端主动把刷盘延后了,写延迟的锅多半在这。把这几项加进监控面板,AOF 的日常就透明了。
重写前:SET k 1 / INCR k / INCR k / … 共 100000 行 重写后:SET k 100001 共 1 行
于是 AOF 的两个经典抱怨都有了着落:文件大(重写压缩)、恢复慢(重写后文件已是最小集,恢复量正比于当前数据量而非历史写入量)。
重写的一个常见误解顺带澄清:重写不是"修改旧文件",而是生成全新文件后原子替换——旧文件在重写完成前始终完整,中途崩溃最多丢掉"新文件没转正"这件事本身,数据仍在旧文件里。所以重写是安全的运维动作,需要防的只是它与其他 fork 活动撞车,而不是它会不会弄坏日志。
⚠️ 常见坑:重写也是 fork,同样吃写时复制的内存红利与代价;高峰期自动重写叠加自动 RDB,内存可能双份峰值。把 auto-aof-rewrite-percentage 调高、用定时任务在低峰手动触发,是老练的配置习惯。
进程被 kill -9 恰好停在写文件半截,AOF 末尾可能出现半条命令。服务启动报"bad truncate"时,用自带的修复工具截掉残行(会丢最后那条残缺命令):
redis-check-aof --fix appendonly.aof
这是"日志是文本"的红利之一——肉眼能看,工具能修。
顺带一句重放时的细节:AOF 恢复过程就是服务端逐条执行历史命令,因此文件里的命令顺序就是唯一的真相。多人手工修文件时务必只做"截断"不做"重排",顺序一动,数据就可能回到另一种中间态。