3. 内存分配与碎片率优化

Redis 的内存问题很少是“总量不够”这么简单。生产上更常见的情况是:used_memory 看起来不高,但 used_memory_rss 却持续上涨,最终触发 OOM 或触发 maxmemory 淘汰。这中间的差值,正是内存分配器与内存碎片在起作用。理解 jemalloc 的分配策略、读懂 mem_fragmentation_ratio、掌握 activedefrag 的配置与整理时机,是 Redis 容量规划中绕不开的一环。

一、jemalloc 的分配策略:为什么 Redis 默认选它

Redis 默认使用 jemalloc 作为内存分配器(Linux 下编译时内置)。它并非简单地把 malloc 包一层,而是围绕“减少碎片、提升并发分配效率”做了专门设计,核心机制有三点:

  • size class 分级:jemalloc 把内存请求划分为多个固定档位(如 8、16、32、48、64、80、96、112、128 字节……),每个档位对应一个 size class。申请 100 字节时,实际分配 112 字节,多出的 12 字节就是内部碎片。这种“向上取整”牺牲少量空间,换来分配与回收的快速定位。
  • arena 与线程缓存:jemalloc 将内存划分为多个 arena,每个线程绑定到某个 arena,并在 tcache(线程缓存)中缓存小对象。这样多线程分配时锁竞争大幅降低,Redis 主线程与后台线程的分配互不干扰。
  • bin 与 run 管理:每个 size class 对应一个 bin,bin 管理若干 run(连续页组成的块),run 再切分为等大的 region 供对象使用。释放的 region 回到 bin,只有整个 run 空闲时才可能归还给系统。

这套机制决定了 Redis 的内存释放是“惰性”的:删除 key 后,内存先回到 jemalloc 的 bin 中,不一定立刻还给操作系统。这正是 RSS 居高不下的重要原因之一。

二、mem_fragmentation_ratio 解读:一个比值背后的三种含义

mem_fragmentation_ratio = used_memory_rss / used_memory,它是判断碎片程度最直接的指标,但绝不能只看“大于 1 就是碎片”。

比值范围含义处理建议
≈ 1.0 ~ 1.5正常范围,RSS 略高于逻辑内存,属分配器预留无需干预
> 1.5碎片偏高,RSS 明显大于逻辑内存观察趋势,考虑 activedefrag
> 2.0 且持续上升严重碎片,可能触发 OOM启用 activedefrag 或重启/迁移
< 1.0RSS 小于逻辑内存,说明发生了 swap危险信号,优先排查 swap

需要特别强调:比值小于 1 比大于 2 更危险。它意味着部分内存被换出到磁盘,Redis 访问这些数据时会产生磁盘 I/O,延迟从微秒级飙升到毫秒级,性能断崖式下跌。生产环境应通过 vm.swappiness=0 或 cgroup 限制尽量避免 swap。

三、内存碎片产生的四类原因

碎片不是单一原因造成的,常见来源包括:

  1. 频繁增删不同大小的 key:大量 key 反复创建和删除,jemalloc 的 bin 中留下大小不一的空闲 region,无法被新对象复用。
  2. 大 key 删除:删除一个占用几 MB 的 hash 或 list,释放的 run 可能因不连续而无法归还系统。
  3. 过期键集中淘汰:同一时间大量 key 过期,释放的内存块分散,形成外部碎片。
  4. 数据量增长后收缩:业务高峰期写入大量数据,之后删除,RSS 不会自动下降,逻辑内存却已回落。
判断碎片是否“值得整理”,不能只看瞬时比值。建议用 INFO memory 连续采样,观察 mem_fragmentation_ratio 是否持续上升,以及 allocator_frag_ratio(分配器自身碎片率)是否同步走高。

四、activedefrag 配置与生产环境碎片整理实战

Redis 4.0 引入 activedefrag,由后台线程在运行时搬运数据、合并空闲块,无需重启。核心配置如下:

# 开启主动碎片整理
activedefrag yes

# 碎片率达到该阈值才开始整理(默认 1.1)
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-threshold-upper 100

# 整理时占用的 CPU 时间比例(默认 20%)
active-defrag-cycle-min 5
active-defrag-cycle-max 75

# 单次整理最大扫描块数
active-defrag-max-scan-fields 1000

参数含义需要结合生产实际理解:active-defrag-ignore-bytes 表示碎片浪费低于 100MB 时不整理,避免小碎片频繁触发;active-defrag-threshold-lower 为 10 表示碎片率超过 10% 开始整理,upper 为 100 表示碎片率到 100% 时全力整理;active-defrag-cycle-min/max 控制后台线程占用 CPU 的比例,低碎片时用 5%,高碎片时最多 75%。

生产环境建议的整理流程是:先用 INFO memory 确认 mem_fragmentation_ratio 持续大于 1.5 且 allocator_frag_ratio 偏高,再开启 activedefrag,观察 active_defrag_running 是否为 1、mem_fragmentation_ratio 是否逐步回落。若整理后比值仍不下降,说明碎片已无法通过搬运解决,需要考虑主从切换或重启实例。

Redis 内存碎片诊断与 activedefrag 整理链路 逻辑内存 used_memory 已用对象数据 已用对象数据 已用对象数据 jemalloc 向上取整产生内部碎片 释放后 region 留在 bin 中 物理内存 used_memory_rss 对象数据 对象数据 空闲 region(碎片) RSS 包含未归还系统的空闲块 碎片率 = RSS / used_memory 碎片率判断 ≈1.0~1.5 正常 >1.5 偏高,观察 >2.0 严重,需整理 <1.0 发生 swap,优先排查 swap 比碎片更危险 activedefrag 后台整理流程 采样 INFO memory 确认碎片率持续偏高 开启 activedefrag 设置阈值与 CPU 比例 后台线程搬运 region 合并空闲块 观察碎片率回落 active_defrag_running 若整理后碎片率仍不下降 → 主从切换 / 重启实例 重启前务必确认持久化与数据一致性

最后给出一个可直接参考的生产检查清单:

检查项命令/参数判断标准
碎片率INFO memory → mem_fragmentation_ratio持续 >1.5 需关注
分配器碎片allocator_frag_ratio>1.2 说明 jemalloc 内部碎片高
是否在整理active_defrag_running1 表示正在整理
swap 风险used_memory_rss < used_memory立即排查 swap
整理效果整理前后 mem_fragmentation_ratio 对比下降 0.2 以上视为有效
activedefrag 会占用 CPU 和内存带宽,在 CPU 已经吃紧的实例上应谨慎开启,建议先用 active-defrag-cycle-max 25 限制占用,观察业务延迟后再逐步放宽。

内存碎片优化不是一次性动作,而是容量规划中的持续观测项。把 jemalloc 的分配行为、碎片率指标和 activedefrag 的整理效果串起来看,才能在 RSS 上涨时快速判断是“正常预留”“碎片累积”还是“swap 风险”,并选择正确的处理路径。