AOF重写因大Key卡住是因遍历并完整序列化大Key(如2GB zset)导致内存暴涨、触发OOM Killer;需用MEMORY USAGE精准定位、禁用incremental-fsync与混合持久化、手动错峰重写,并通过SCAN+Lua分批迁移清理。

为什么AOF重写会因大Key卡住
AOF重写不是简单复制日志,而是遍历所有Key、重新序列化后生成紧凑新文件。遇到一个2GB的zset,Redis必须把它完整读入内存再编码写入——这过程不光吃CPU,还会额外占用等量RSS内存。系统一旦触发OOM Killer,bgrewriteaof直接被杀,AOF文件损坏,重启失败。
典型信号包括:INFO persistence里aof_rewrite_in_progress长期为1、used_memory_rss在重写期间暴涨、mem_fragmentation_ratio > 1.5且持续上升。
用MEMORY USAGE精准定位真实大Key
DEBUG OBJECT返回的是估算值,不准;redis-cli --bigkeys只看元素数量或字符串长度,漏掉编码膨胀(比如hashtable比ziplist多占3–5倍内存)。唯一可靠方式是逐个测:
-
MEMORY USAGE huge_zset—— 返回真实字节数,含指针、哈希表冗余、编码开销 - 对疑似Key批量检测:用
SCAN游标配合EVAL脚本,避免全量阻塞 - 重点关注
encoding字段:若hash显示hashtable但元素仅几千,说明单field value过大或hash-max-ziplist-value配置过小,已提前退化
避免AOF重写期间内存雪崩的实操配置
默认aof-rewrite-incremental-fsync yes会在重写时每32MB做一次fdatasync,看似平滑,实则加剧I/O抖动+延长重写时间,给大Key留出更长的内存驻留窗口。改成no可让重写“一气呵成”,缩短高内存占用时段:
- 执行
CONFIG SET aof-rewrite-incremental-fsync no(需确认磁盘吞吐足够) - 关闭混合持久化:
CONFIG SET aof-use-rdb-preamble no,避免RDB preamble阶段再fork一次+序列化同一份大Key - 收紧客户端缓冲区:
CONFIG SET client-output-buffer-limit normal 0 0 0,防止重写期间订阅者积压响应拖垮内存 - 禁用自动触发:
CONFIG SET auto-aof-rewrite-percentage 0,改由运维在低峰期手动bgrewriteaof
无法删、不能拆的大Key怎么安全清理
有些大Key业务强依赖,不能直接UNLINK(异步删只解决DEL阻塞,不解决重写时的内存压力),也不能拆分(如全局计数器)。这时必须绕过“全量序列化”这个死结:
- 用
SCAN+HSCAN/ZSCAN分批读取,每次最多COUNT 100,写入新结构 - 用Lua脚本原子性地迁移+清空,例如:
EVAL "local v=redis.call('HGETALL',KEYS[1]); redis.call('HMSET',KEYS[2],unpack(v)); redis.call('DEL',KEYS[1])" 2 old_hash new_hash - 迁移完成后,用
RENAME切换,全程无大Key参与AOF重写 - 若连SCAN都慢,说明底层编码已严重退化,优先考虑
CONFIG SET hash-max-ziplist-entries 1000等调参重建结构
真正棘手的从来不是“怎么删”,而是“删之前没监控到”。MEMORY USAGE必须纳入每日巡检,而不是等latest_fork_usec飙到5秒才翻日志。


















