Karp 的技术博客

在 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_memoryRedis 分配器认为已使用的内存
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 大量删除操作

频繁 DELEXPIRE 过期 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_running

5.2 查看 key 分布

# 查看总 key 数量
redis-cli DBSIZE

# 查看各数据库 key 分布
redis-cli INFO keyspace

# 扫描大 key(谨慎在生产使用)
redis-cli --bigkeys

5.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

版权属于:karp
作品采用:本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
更新于: 2026年03月09日 02:13
0

目录

来自 《 [踩坑] Redis 内存碎片问题深度解析与解决方案》