Redis的内存由底层allocator(默认 jemalloc,CentOS等系统编译时也可能是libc)统一管理,键值对象的创建和销毁会不断向allocator申请、归还内存。时间一长,就会出现一个奇怪的现象:数据总量没怎么变,RSS却居高不下,INFO memory里mem_fragmentation_ratio超过了1.5甚至更高。这就是典型的内存碎片问题。Redis 4.0引入的active-defrag功能允许实例在不重启、不阻塞请求的情况下自动搬迁数据、整理碎片,本文详细拆解这套机制的工作原理和参数调优。

内存碎片是怎么产生的
要理解active-defrag,得先明白碎片从哪来。Redis进程向操作系统申请内存是以页为单位的,通常一页4KB。而allocator拿到这些页之后,会自己切成各种大小的内存块(size class)提供给Redis使用。jemalloc的一大优势就是按固定规格分配,比如16字节、32字节、48字节这样的梯度,来减少内部碎片。
问题出在对象的分配和释放顺序上。假设一个hash里有一千个field,每个field占据了一块48字节的内存。当这个hash的一部分field被删除,或者某些field的值变大需要重新分配时,原来那块48字节的内存就空出来了。但是这块空闲内存的两侧如果都被仍然存活的对象占着,allocator没法把它归还给操作系统,只能留着复用。成千上万次这样的操作叠加,就会出现大量散落在各处的空闲小块,从Redis的视角看这些内存"可回收",从操作系统的视角看RSS一直不降。
碎片率计算公式很简单:mem_fragmentation_ratio = used_memory_rss / used_memory。一般经验值是1.0到1.5之间属于正常,超过1.5说明碎片偏高,超过2就值得警惕了。另外要注意两种容易误判的情况:一是实例刚启动时used_memory很小,比值会虚高;二是开启了swap或者THP(透明大页)也会导致RSS异常,判断前先排除这些因素。
active-defrag的工作原理
active-defrag的核心思想是"搬家":既然那些存活对象东一个西一个地把空闲内存割裂开了,那就把它们搬到一起,把分散的空闲块合并成连续的大块,allocator就能把整页归还给操作系统。Redis的做法是在每个事件循环的beforeSleep阶段,评估当前碎片率,如果达到触发条件,就拿出一点CPU时间去扫描键空间,挑出一部分键进行搬迁。
搬迁的具体过程是:遍历字典中的键,对每个键反序列化其内容,重新分配一块新的内存把数据拷贝进去,然后原子地替换旧对象并释放旧内存。新分配走的是allocator的size class逻辑,通常落在紧凑的、已经整理过的区域。搬迁完成后,旧区域里腾出的空闲块逐渐连成片,触发allocator的purge逻辑把整页还给内核,RSS随之下降。整个过程是增量进行的,每次只处理一小批键,通过限流参数控制开销,所以对线上请求的影响可控但不为零。
这项功能依赖allocator提供精确的内部碎片统计,所以只有jemalloc支持得比较好,如果Redis是链接libc malloc编译的,开启active-defrag会直接报错拒绝启动。查看方式是执行INFO server看mem_allocator字段,确认是jemalloc再考虑启用。
核心参数详解与配置示例
active-defrag不是简单一个开关,而是一组互相配合的参数。下面是生产上比较常用的配置:
# redis.conf 关键配置 activedefrag yes # 碎片整理的最低启动条件: # 1. 空闲字节数(浪费的内存)超过该值才考虑整理,默认100mb active-defrag-ignore-byte 100mb # 2. 碎片率超过 lower 才开始,低于 min-lower 不动 active-defrag-threshold-lower 10 # 3. 碎片率超过 upper 时达到最大整理力度 active-defrag-threshold-upper 100 # CPU开销的上下限(百分比) # 整理线程占用CPU的最小和最大比例 active-defrag-cycle-min 1 active-defrag-cycle-max 25 # 每次搬迁写入新内存的最小和最大比例 # 相对总内存而言,控制单次处理的吞吐量 active-defrag-max-scan-fields 1000
几个参数的逻辑关系值得梳理一下。threshold-lower和threshold-upper定义了一个"力度曲线":碎片率处于lower和upper之间时,整理力度随碎片率线性增长,从cycle-min逐步上升到cycle-max。碎片率刚过lower时gentle处理,只花1%的CPU;碎片严重时最多用25%的CPU全力整理。ignore-byte是个门槛值,防止小实例因为几MB的碎片就白白消耗CPU。
max-scan-fields针对的是集合类对象:搬迁一个包含十万个元素的hash或set代价不小,这个参数限制单个键内部最多扫描多少个元素,避免大键卡住整理流程。如果线上大hash比较多,可以考虑适当调小这个值,用更细的粒度分多次完成搬迁。
线上启用要注意的坑
第一个坑是CPU争抢。碎片整理发生在主线程的事件循环里,并不是独立线程,cycle-max设得太高会直接挤压正常命令的处理时间。对于延迟敏感的业务,建议cycle-max从默认25降到10以内,观察INFO stats中的latency和CPU负载后再逐步放宽。
第二个坑是和COPY迁移类操作叠加。当实例同时在做RDB子进程dump、主从全量同步时,内存页的拷贝量本来就大,碎片整理会加剧COW(写时复制)的内存放大效应。虽然Redis内部做了一些互斥处理,但从运维角度,最好在低峰期启用active-defrag,并确认没有同时进行的大规模数据导入。
第三个坑是期望管理。active-defrag不是万能药,它只能整理键值对象造成的碎片,如果是下列情况导致的RSS偏高就无能为力:客户端连接缓冲区堆积、replication backlog过大、THP开启导致的页粒度浪费。动手之前先用MEMORY DOCTOR和MEMORY STATS做个体检,确认碎片确实是主因。配置生效后持续观察mem_fragmentation_ratio,一般几小时到几天内会逐步回落到1.3以下,如果纹丝不动,大概率是参数门槛没达到或者allocator不支持,回头检查基础条件往往比盲目调参更有效。
Redisactive-defrag内存碎片整理修改时间:2026-09-16 08:38:39