在 Redis 运维过程中,你是否遇到过这样的困惑:明明已经删除了大量数据,used_memory 也确实下降了,但服务器的实际内存占用却几乎没有变化?
这就是 Redis 内存碎片问题,也是生产环境中最常见、最容易被忽视的性能隐患之一。本文将从原理到实战,全面解析 Redis 内存碎片的成因、诊断方法和解决方案。
一、什么是内存碎片?
1.1 内存分配原理
Redis 并不直接使用操作系统的 malloc,而是使用专门的内存分配器(默认为 jemalloc)。内存分配器为了提高效率,会按固定大小的块来分配内存,例如:
申请 10 bytes → 实际分配 16 bytes
申请 20 bytes → 实际分配 32 bytes
申请 65 bytes → 实际分配 96 bytes多出来的部分就是内部碎片。
1.2 碎片是如何产生的
初始状态:
[Key A: 100bytes] [Key B: 200bytes] [Key C: 150bytes] [Key D: 300bytes]
删除 Key B 和 Key C 后:
[Key A: 100bytes] [ 空闲 350bytes ] [Key D: 300bytes]
新写入 Key E: 500bytes:
无法填入中间空隙 → 只能在末尾申请新内存
[Key A: 100bytes] [ 碎片 350bytes ] [Key D: 300bytes] [Key E: 500bytes]这就是外部碎片——内存空间存在,但因为不连续而无法被利用。
二、真实案例分析
以下是一个生产环境中的实际数据:
# 执行 MEMORY PURGE 前
used_memory: 10.54G ← Redis 认为自己用了多少
used_memory_rss: 25.32G ← 操作系统实际分配了多少
mem_fragmentation_ratio: 2.40 ← 碎片率(严重!)
maxmemory_policy: noeviction
# 执行 MEMORY PURGE 后
used_memory: 10.54G ← 数据量不变
used_memory_rss: 22.71G ← 释放了 2.61G
mem_fragmentation_ratio: 2.16 ← 有所下降,但仍偏高结论: 数据只用了 10.54G,但系统实际占用 22.71G,约 12G 内存被碎片浪费。
三、核心指标解读
3.1 关键字段说明
| 字段 | 含义 |
|---|---|
used_memory | Redis 分配器认为已使用的内存 |
used_memory_rss | 操作系统视角的实际物理内存占用 |
used_memory_peak | 历史内存峰值 |
mem_fragmentation_ratio | 内存碎片率 = rss / used_memory |
active_defrag_running | 自动碎片整理是否正在运行(1=是) |
3.2 碎片率健康标准
mem_fragmentation_ratio < 1.0 → 内存不足,可能使用了 Swap(危险)
mem_fragmentation_ratio 1.0~1.5 → 正常范围 ✅
mem_fragmentation_ratio 1.5~2.0 → 碎片偏多,需要关注 ⚠️
mem_fragmentation_ratio > 2.0 → 碎片严重,需要立即处理 🚨四、碎片产生的常见原因
4.1 大量删除操作
频繁 DEL、EXPIRE 过期 key,留下大量不连续的空洞。
4.2 Value 大小变化频繁
# 先写入小 value
SET user:1 "hello"
# 再更新为大 value
SET user:1 "hello world this is a very long string..."原来的空间不够用,分配器重新分配,旧空间变成碎片。
4.3 使用了不合适的数据结构
- 大量小 String 替代 Hash → 碎片率更高
- ziplist 升级为 hashtable 后旧内存未释放
4.4 jemalloc 版本较旧
旧版 jemalloc(如 4.0.3)碎片整理能力较弱,建议升级 Redis 版本。
五、诊断方法
5.1 查看内存信息
# 查看完整内存信息
redis-cli INFO memory
# 只看碎片率
redis-cli INFO memory | grep mem_fragmentation_ratio
# 查看自动整理状态
redis-cli INFO memory | grep active_defrag_running5.2 查看 key 分布
# 查看总 key 数量
redis-cli DBSIZE
# 查看各数据库 key 分布
redis-cli INFO keyspace
# 扫描大 key(谨慎在生产使用)
redis-cli --bigkeys5.3 内存使用分析
# 查看指定 key 内存占用
redis-cli MEMORY USAGE keyname
# 查看内存分配器统计
redis-cli MEMORY STATS六、解决方案
6.1 方案一:手动触发整理(快速见效)
# 立即释放碎片内存
redis-cli MEMORY PURGE优点: 立即执行,效果明显
缺点: 一次性操作,不能持续整理,可能短暂影响性能
6.2 方案二:开启自动碎片整理(推荐)
# 动态开启,无需重启
redis-cli CONFIG SET activedefrag yes
# 碎片超过 100MB 才开始整理
redis-cli CONFIG SET active-defrag-ignore-bytes 100mb
# 碎片率超过 10% 开始整理
redis-cli CONFIG SET active-defrag-threshold-lower 10
# 碎片率超过 100% 加大整理力度
redis-cli CONFIG SET active-defrag-threshold-upper 100
# CPU 使用率下限(整理占用 CPU 不低于此值)
redis-cli CONFIG SET active-defrag-cycle-min 1
# CPU 使用率上限(整理占用 CPU 不超过此值)
redis-cli CONFIG SET active-defrag-cycle-max 25写入配置文件永久生效:
# redis.conf
activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-threshold-upper 100
active-defrag-cycle-min 1
active-defrag-cycle-max 25验证是否在运行:
redis-cli INFO memory | grep active_defrag_running
# 1 = 正在整理,0 = 未整理6.3 方案三:重启 Redis(最彻底)
# 第一步:持久化数据
redis-cli BGSAVE
redis-cli BGREWRITEAOF
# 第二步:等待持久化完成
redis-cli INFO persistence | grep rdb_bgsave_in_progress
# 返回 0 表示完成
# 第三步:重启服务
systemctl restart redis⚠️ 重启会造成短暂服务中断,生产环境建议在低峰期操作,或使用主从切换方式滚动重启。
6.4 方案四:主从切换滚动重启(生产推荐)
# 1. 对从节点执行重启
systemctl restart redis-slave
# 2. 观察从节点内存恢复正常
redis-cli -h slave-ip INFO memory | grep mem_fragmentation_ratio
# 3. 执行主从切换
redis-cli -h master-ip DEBUG SLEEP 0
# 或使用 Sentinel/Cluster 自动切换
# 4. 对旧主节点(现从节点)执行重启
systemctl restart redis-master七、预防措施
7.1 合理设置淘汰策略
# 当前危险配置
maxmemory-policy noeviction # 内存满了直接报错
# 推荐配置
maxmemory-policy allkeys-lru # 淘汰最久未使用的 key| 策略 | 适用场景 |
|---|---|
noeviction | 数据不能丢失,需要严格控制 key 数量 |
allkeys-lru | 缓存场景,允许自动淘汰 ⭐ |
volatile-lru | 只淘汰有过期时间的 key |
allkeys-random | 随机淘汰,不推荐 |
7.2 为 key 设置过期时间
# 避免永久 key 堆积
SET session:user:1 "data" EX 3600 # 1小时过期
SET cache:product:100 "data" EX 86400 # 1天过期7.3 使用合适的数据结构
# 不推荐:大量小 String
SET user:1:name "Alice"
SET user:1:age "25"
SET user:1:city "Beijing"
# 推荐:使用 Hash 聚合
HSET user:1 name "Alice" age "25" city "Beijing"7.4 监控告警
# 写入监控脚本
#!/bin/bash
FRAG=$(redis-cli INFO memory | grep mem_fragmentation_ratio | awk -F: '{print $2}' | tr -d '\r')
echo "当前碎片率: $FRAG"
# 超过 1.5 告警
if (( $(echo "$FRAG > 1.5" | bc -l) )); then
echo "⚠️ 碎片率超标,请检查 Redis 内存!"
fi八、操作流程总结
发现碎片率高(> 1.5)
↓
执行 MEMORY PURGE 快速释放
↓
开启 activedefrag 持续整理
↓
观察 mem_fragmentation_ratio 变化
↓
若仍 > 2.0 → 考虑低峰期重启
↓
调整 maxmemory-policy 防止复发九、常见问题 FAQ
Q:开启 activedefrag 会影响性能吗?
会有轻微影响,但通过 active-defrag-cycle-max 25 限制 CPU 占用在 25% 以内,生产环境基本无感知。
Q:used_memory 和 used_memory_rss 哪个是真实占用?
used_memory_rss 是操作系统视角的真实物理内存占用,是更准确的参考值。
Q:为什么重启后碎片率会降为 1.0 左右?
重启后 Redis 重新加载 RDB/AOF 数据,内存重新分配,碎片全部消除,是最彻底的整理方式。
Q:jemalloc 和 libc 哪个碎片率更低?
jemalloc 的碎片控制能力优于 libc,Redis 默认使用 jemalloc,无需更换。
十、总结
| 方案 | 效果 | 风险 | 适用场景 |
|---|---|---|---|
| MEMORY PURGE | 中等 | 低 | 临时快速处理 |
| activedefrag | 持续 | 极低 | 生产长期运行 ⭐ |
| 重启 Redis | 彻底 | 中 | 低峰期维护 |
| 主从滚动重启 | 彻底 | 低 | 高可用生产环境 ⭐ |
Redis 内存碎片是不可避免的,关键在于持续监控 + 自动整理 + 合理配置,将碎片率长期控制在 1.5 以内,才能保证 Redis 稳定高效运行。
参考资料
- Redis 官方文档 - Memory Optimization
- Redis CONFIG SET activedefrag
- jemalloc 内存分配器文档
redis-cli INFO memory命令手册