fork导致的mem_fragmentation_ratio突增多为COW虚高,非真实碎片;需通过used_memory_peak是否变化、jemalloc版本及malloc-stats中bytes mapped与active比值来区分,再决定是否干预。

持久化期间 fork 子进程会放大 jemalloc 碎片感知偏差
Redis 执行 RDB 或 AOF rewrite 时,必须调用 fork() 创建子进程。此时操作系统采用写时复制(Copy-on-Write)机制,父进程的内存页在被修改前与子进程共享。但 jemalloc 的内存统计逻辑基于“当前活跃分配”,而 fork 后父子进程各自维护独立的分配器状态——子进程启动时会复制父进程的 arena 元信息,但不真正复制内存块;父进程继续分配新内存时,可能落在已被子进程“逻辑持有”但物理未复制的页上,导致 used_memory_rss 在内核视角持续增长,而 used_memory(由 Redis 主线程统计)未同步反映这部分隐式占用。
这种统计脱节不是真实碎片,而是 rss 虚高。现象表现为:持久化刚结束时 mem_fragmentation_ratio 突然跳到 2.0+,但过几分钟又回落——本质是内核延迟回收了那些未被实际写入的 COW 页。
RDB/AOF rewrite 过程中高频重分配触发 jemalloc 内部碎片累积
当 Redis 执行 bgrewriteaof 或 bgsave,主线程仍在服务请求。若此时有大量键被更新(尤其是 HSET/APPEND/INCR 类操作),jemalloc 会频繁为变长结构(如 dict、ziplist、quicklist)分配/释放不同尺寸的内存块。而 jemalloc 的 size class 是离散的(如 16B/32B/48B/64B…),小尺寸对象膨胀后需升级槽位,旧槽位无法被后续更小对象复用,形成外部碎片。
这类碎片在持久化期间尤其明显,因为:
- 主线程压力未减,但后台子进程占用了部分 CPU 和内存带宽,延迟了 jemalloc 的
purge周期 -
bgrewriteaof本身会遍历所有键生成新 AOF,触发大量redisObject临时构造与销毁 - 如果启用了
auto-aof-rewrite-percentage,AOF 重写可能在业务高峰自动触发,叠加效应加剧碎片
碎片率飙升后是否需要立即干预?
不能只看 mem_fragmentation_ratio 数值跳升就执行 MEMORY PURGE 或重启。先确认是否属于 COW 虚高:
- 检查
used_memory_peak是否同步上涨:若used_memory_peak几乎不变,仅used_memory_rss拉高,大概率是 fork 导致的瞬时偏差 - 观察
mem_allocator是否为jemalloc-5.x:老版本 jemalloc(如 3.x)对 COW 场景统计更不准确 - 用
MEMORY MALLOC-STATS查看arenas下的bytes active与bytes mapped比值,若后者远大于前者,说明存在大量映射但未激活的内存,属典型 COW 行为
真实碎片(非 COW 引起)往往伴随 used_memory 持续缓慢上升、MEMORY STATS 中 allocator_frag_bytes 显著增加,且低峰期也不回落——这时才需调整 jemalloc 参数或启用 active-defrag。
如何降低持久化对碎片率的扰动
根本思路是减少 fork 时机与主线程内存抖动的耦合:
- 避开业务高峰执行
SAVE或手动BGREWRITEAOF;生产环境建议用CONFIG SET auto-aof-rewrite-percentage 0关闭自动重写,改为定时脚本在凌晨低峰触发 - 将
hz从默认 10 提至 100~200,加快 jemalloc 的decay回收节奏,缓解重写期间的碎片堆积 - 禁用透明大页:
echo never > /sys/kernel/mm/transparent_hugepage/enabled,避免 COW 时整页复制放大 rss - 对高频更新的 key,改用
STRLEN + SET替代APPEND,或预估长度使用SET一次性写入,减少字符串扩容次数
最易被忽略的一点:fork 本身不产生碎片,但它把原本平滑的内存分配行为打散成“突发+持续写入”的组合模式,让 jemalloc 的碎片控制策略失敏。处理时别只盯着 MEMORY PURGE,先分清是“统计假象”还是“真实泄漏”。











