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.0 | RSS 小于逻辑内存,说明发生了 swap | 危险信号,优先排查 swap |
需要特别强调:比值小于 1 比大于 2 更危险。它意味着部分内存被换出到磁盘,Redis 访问这些数据时会产生磁盘 I/O,延迟从微秒级飙升到毫秒级,性能断崖式下跌。生产环境应通过 vm.swappiness=0 或 cgroup 限制尽量避免 swap。
三、内存碎片产生的四类原因
碎片不是单一原因造成的,常见来源包括:
- 频繁增删不同大小的 key:大量 key 反复创建和删除,jemalloc 的 bin 中留下大小不一的空闲 region,无法被新对象复用。
- 大 key 删除:删除一个占用几 MB 的 hash 或 list,释放的 run 可能因不连续而无法归还系统。
- 过期键集中淘汰:同一时间大量 key 过期,释放的内存块分散,形成外部碎片。
- 数据量增长后收缩:业务高峰期写入大量数据,之后删除,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 是否逐步回落。若整理后比值仍不下降,说明碎片已无法通过搬运解决,需要考虑主从切换或重启实例。
最后给出一个可直接参考的生产检查清单:
| 检查项 | 命令/参数 | 判断标准 |
|---|---|---|
| 碎片率 | INFO memory → mem_fragmentation_ratio | 持续 >1.5 需关注 |
| 分配器碎片 | allocator_frag_ratio | >1.2 说明 jemalloc 内部碎片高 |
| 是否在整理 | active_defrag_running | 1 表示正在整理 |
| swap 风险 | used_memory_rss < used_memory | 立即排查 swap |
| 整理效果 | 整理前后 mem_fragmentation_ratio 对比 | 下降 0.2 以上视为有效 |
active-defrag-cycle-max 25 限制占用,观察业务延迟后再逐步放宽。内存碎片优化不是一次性动作,而是容量规划中的持续观测项。把 jemalloc 的分配行为、碎片率指标和 activedefrag 的整理效果串起来看,才能在 RSS 上涨时快速判断是“正常预留”“碎片累积”还是“swap 风险”,并选择正确的处理路径。