欢迎光临
我们一直在努力

Redis 删了 2GB 数据,内存却纹丝不动?深挖内存碎片的真相

Redis 删了 2GB 数据,内存却纹丝不动?深挖内存碎片的真相

你有没有遇到过这样诡异的场景:明明已经删除了大量 Key,用 top 命令一看,Redis 进程内存占用依然高居不下;或者 info memory 告诉你 Redis 存储的数据只有 1GB,但操作系统却显示进程吃掉了 3GB 内存,还在报"内存不足"的错误?

别慌,今天我们就来拆穿这个让无数 Redis 用户抓狂的"内存幻术"——
内存碎片。


一、先从一个灵魂问题开始

假设你的 Redis 实例保存了 5GB 的数据,现在你删除了 2GB,此时 Redis 进程占用的内存会降到 3GB 左右吗?

答案:大概率不会。

很可能你用 top 命令看到的 RSS(进程实际占用的物理内存页数)依然在 5GB 左右徘徊,即便 Redis 里存储的数据只剩 3GB。

这不是 Bug,也不是 Redis 在耍你——背后有一套完整的内存管理逻辑。搞清楚这套逻辑,你才能真正驾驭 Redis。


二、Redis 的内存从哪来,又到哪去了?

2.1 先设好 maxmemory,这是底线

在深入原理之前,有一条铁律要先说清楚:

# 通过命令动态设置
CONFIG SET maxmemory 100mb

# 或者在 redis.conf 中配置
maxmemory 100mb

一定要设置 maxmemory!

如果不设,Redis 会无限制地为新数据分配内存,直到系统内存耗尽,最终导致应用程序报错(OOM)。虽然不至于宕机,但你的业务已经完蛋了。

当内存达到上限时,会触发内存淘汰策略(maxmemory_policy),决定删除哪些数据腾出空间。

2.2 Redis 的内存都花在哪里了?

Redis 进程的内存消耗主要由四个部分构成:

Redis 进程内存
├── 自身启动内存(可忽略,很小)
├── 对象数据内存(大头,存储所有 Key-Value 数据)
├── 缓冲区内存
│ ├── client-output-buffer-limit(客户端输出缓冲区)
│ ├── 复制积压缓冲区(主从复制用)
│ └── AOF 缓冲区(持久化用)
└── 内存碎片(隐形杀手)

  • 对象数据内存:占比最大,所有 Key-Value 都在这里
  • 缓冲区内存:大流量场景容易失控,需要重点监控
  • 内存碎片:隐形杀手,明明有空间却无法使用

2.3 用 info memory 看透内存状况

想知道 Redis 内存的真实情况,info memory 是你最好的朋友:

127.0.0.1:6379> info memory

# Memory
used_memory:1132832 # Redis 存储数据实际占用的内存
used_memory_human:1.08M # 人类可读形式
used_memory_rss:2977792 # 操作系统视角,进程占用的物理总内存(RSS)
used_memory_rss_human:2.84M
used_memory_peak:1183808 # 历史内存占用峰值
used_memory_peak_human:1.13M
used_memory_lua:37888 # Lua 引擎消耗的内存
maxmemory:2147483648 # 最大可用内存(字节)
maxmemory_human:2.00G
maxmemory_policy:noeviction # 当前内存淘汰策略

mem_fragmentation_ratio:2.79 # 内存碎片率(重点关注!)

其中最关键的指标是 mem_fragmentation_ratio,也就是内存碎片率:

内存碎片率 = used_memory_rss(OS 分配给 Redis 的物理内存)
÷ used_memory(Redis 实际存储数据的内存)

上面的例子里碎片率是 2.79,意味着操作系统分配给 Redis 2.84MB 的物理内存,但 Redis 只用其中 1.08MB 存了数据,另外 1.76MB 都是碎片和开销。


三、内存碎片是什么?一个电影院的比喻

光说数字可能还不够直观,来个生活化的例子。

你和漂亮小姐姐去电影院看电影,肯定希望坐在一起。现在影厅有 8 个座位,已经卖出了 4 张票,还剩 4 个空位。

但你打开选座界面一看:

座位分布:
[已售] [空闲] [已售] [空闲] [已售] [空闲] [已售] [空闲]

好巧不巧,买票的人每隔一个座位买一张,导致 4 个空座位全是分散的,无法买到两张相邻的票。你有再多的钱,也买不到两张连座。

内存碎片的本质就是这个问题: 空闲内存总量够用,但不是连续的,无法满足新的数据存储请求。


四、内存碎片是怎么产生的?

内存碎片的产生主要有两大根源。

4.1 内存分配器的"好意"

Redis 默认使用 jemalloc 作为内存分配器(也可选 glibc、tcmalloc)。

内存分配器并不是你要多少就给多少,而是按照固定大小的内存块分配,比如 8 字节、16 字节、32 字节……2KB、4KB 等固定档位。

当你申请内存时,jemalloc 会分配最接近且不小于申请量的固定大小空间。

举个例子:

程序申请 1.5 KB → jemalloc 分配 2 KB → 多出 0.5 KB(碎片)
程序申请 22 字节 → jemalloc 分配 32 字节 → 多出 10 字节(碎片)

这么做并非毫无道理——减少内存分配次数,提升性能。比如你先写入 22 字节,jemalloc 分配了 32 字节。后续再写入 10 字节时,不需要再向操作系统申请内存,直接用之前剩余的空间就行了。

但代价就是,每次分配都会产生少量"边角料"碎片。

4.2 Key-Value 频繁修改和删除

这是碎片产生的更主要原因。

场景一:修改 Key 导致碎片

原始字符串占用 32 字节的内存块
修改后字符串只需要 20 字节
→ 释放了 12 字节的空间

下一个请求需要申请 13 字节
→ 刚释放的 12 字节空间不够用,只能另外申请新空间
→ 原来的 12 字节沦为碎片

场景二:删除 Key 导致 RSS 不降

删除 Key 之后,Redis 并不会立即把内存归还给操作系统。原因是:底层内存分配器的管理机制——大多数已删除的 Key 依然与其他有效 Key 分配在同一个内存页中,操作系统无法单独回收其中一部分内存页。

这就解释了为什么删了 2GB 数据,RSS 依然显示 5GB。

不过这里有个好消息:当你再次往 Redis 写入数据时,RSS 不会继续增长,因为内存分配器会优先复用之前释放的内存块。


五、碎片率多少算危险?

mem_fragmentation_ratio < 1 → 异常!物理内存不够用,Redis 在用 swap,性能会极差
1 ≤ mem_fragmentation_ratio < 1.5 → 正常,合理范围
mem_fragmentation_ratio ≥ 1.5 → 警告!碎片超过 50%,需要处理

当碎片率大于 1.5,意味着 操作系统分配给 Redis 的内存中,有超过 1/3 都是碎片,不仅是资源浪费,还可能导致"有内存却存不了数据"的诡异问题。


六、解决内存碎片的两种方式

6.1 重启大法(简单粗暴,慎用)

最直接的方式就是重启 Redis,重启后内存重新分配,碎片自然消失。

但代价不小:

  • 未开启持久化:数据全部丢失
  • 开启了持久化:重启后需要用 RDB 或 AOF 恢复数据,数据量大时恢复时间很长,期间服务不可用,高可用性大打折扣

除非数据允许丢失或数据量极小,否则不建议把重启当成常规手段。

6.2 自动清理内存碎片(Redis 4.0+ 推荐方案)

Redis 4.0 版本引入了自动内存碎片清理机制(Active Defragmentation)。

原理

类比电影院的场景:已经在电影院里的观众互相协商,把座位挪一挪,让大家都聚在一起,空出来一块连续的座位区。

对 Redis 而言:操作系统把分散在不同位置的数据依次挪动拼接到连续的内存空间,再把原来占用的空间释放出来,形成大块连续的空闲内存。

开启自动清理

CONFIG SET activedefrag yes

触发清理的条件(两个条件同时满足才会触发)

# 条件一:碎片占用的内存绝对值达到 200MB
active-defrag-ignore-bytes 200mb

# 条件二:碎片空间占 Redis 总分配空间的比例超过 20%
active-defrag-threshold-lower 20

控制清理对性能的影响

自动清理涉及内存数据的复制移动,会占用 CPU 资源,影响 Redis 处理请求的性能。通过以下两个参数控制 CPU 占用比例:

# 清理过程中,占用 CPU 的时间比例下限(保证清理任务能正常推进)
active-defrag-cycle-min 20 # 最少占用 20% CPU

# 清理过程中,占用 CPU 的时间比例上限(超过立即停止,避免影响业务)
active-defrag-cycle-max 50 # 最多占用 50% CPU

这两个参数让清理任务在"彻底清理"和"不影响业务"之间找到平衡点——既不会因为清理任务太猛拖慢业务,也不会因为资源太少导致碎片永远清不干净。


七、一张图搞懂全流程

Redis 内存问题排查与处理流程


执行 info memory
查看 mem_fragmentation_ratio

┌───────┴───────┐
│ │
< 1.5 ≥ 1.5
正常, 碎片过多
无需处理 需要处理

┌─────────┴──────────┐
│ │
能接受短暂不可用? 不能接受停服?
│ │
重启 开启自动碎片清理
(数据量小时可用) activedefrag yes
+ 合理设置触发条件
+ 合理设置 CPU 占用上限


八、完整配置参考

# 1. 限制 Redis 最大内存(必须设置!)
maxmemory 4gb
maxmemory_policy allkeys-lru

# 2. 开启自动内存碎片清理
activedefrag yes

# 3. 触发清理的条件
active-defrag-ignore-bytes 200mb
active-defrag-threshold-lower 20

# 4. 控制清理对性能的影响
active-defrag-cycle-min 20
active-defrag-cycle-max 50

性能问题排查 Tip: 如果你发现 Redis 某段时间内响应变慢,怀疑是碎片清理在捣鬼,先看下 activedefrag 是否开启,再把 active-defrag-cycle-max 调小(比如从 50 调到 25),观察延迟是否恢复正常。


九、总结

问题原因解决方案
删数据后 RSS 不降 内存分配器不立即归还内存给 OS 正常现象,再写入数据时会复用
碎片率 > 1.5 jemalloc 按固定块分配 + 频繁删改 Key 开启 activedefrag yes
自动清理影响性能 内存数据复制移动消耗 CPU 调小 active-defrag-cycle-max
Redis 报内存不足 未设置 maxmemory 立即设置 maxmemory + 淘汰策略

记住这个公式和这个阈值:

内存碎片率 = used_memory_rss ÷ used_memory

碎片率 < 1.5 → 无需处理
碎片率 ≥ 1.5 → 开启自动清理

Redis 删了数据内存没降,不是 Redis 的锅,是内存碎片在作祟。搞懂原理,配好参数,让 Redis 的每一个字节都物尽其用。


💬 如果这篇文章对你有帮助,欢迎点赞收藏! 有 Redis 内存相关的坑踩过的,评论区聊聊~

赞(0)
未经允许不得转载:171主机测评 » Redis 删了 2GB 数据,内存却纹丝不动?深挖内存碎片的真相
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址