Java

Redis 内存一直涨,先别急着怪大 key,先看涨的是哪一段

Redis 内存上涨时,现场最常见的误判就是一上来就找大 key 或直接扩容。真正更值钱的第一步,是先分清涨的是数据集、RSS,还是客户端相关内存。

  • Redis
  • 缓存
  • 大key
  • 性能排查
  • 内存问题
10 分钟阅读

Redis 内存一往上走,现场十有八九会先冒出一句话:

  • 是不是大 key 了?

这句话不能说错,但它经常把排查带偏。

因为很多时候,大家嘴里说的是“大 key”,心里想的是“赶紧找到一个明确坏点”,可 Redis 内存上涨这件事,本来就不一定是同一类问题。

有时真的是数据集在变大。 有时是 used_memory 没涨多少,RSS 却在往上爬。 还有时根本不是业务 key,而是客户端输出缓冲、复制积压或者 fork 带出来的额外占用。

如果这几段不先拆开,后面排查很容易一路跑偏:

  • 把 RSS 当成业务数据膨胀
  • 把客户端缓冲当成 key 变多
  • 把碎片率高误判成必须立刻删数据
  • 最后不是问题没解决,就是做了一堆伤害性动作

所以我现在看 Redis 内存上涨,第一反应反而不是“先找大 key”,而是另一句更朴素的话:

先别急着下结论,先看这次到底是哪一段在涨。

这篇文章就只想把这一条线写清楚。

最常见的误判:看到内存涨,就默认是缓存数据失控

这个误判为什么常见?因为监控图通常先给人的就是一个总量数字。

内存从 60% 到 75%,再到 85%,大家天然会把它理解成“缓存里的东西越来越多了”。

但 Redis 的“内存涨了”,至少可能有三种完全不同的现场:

第一种:数据集真的在涨

这时更像是:

  • 新 key 持续写入
  • 旧 key 没有按预期退出
  • TTL 设计有问题
  • 某类集合或对象规模在膨胀

这一类才接近大家直觉里的“缓存真的装太多了”。

第二种:业务数据没涨多少,但 RSS 在涨

这时常见原因反而是:

  • 内存碎片率高
  • 大量写删带来的 allocator 回收不理想
  • fork 期间 copy-on-write 把 RSS 放大
  • active defrag 没跟上

这种场景下,如果你只是盯着“总内存占用变大”,很容易以为是 key 变多了,实际可能并不是。

第三种:涨的不是数据,而是客户端和复制相关占用

这类问题在值班现场特别容易被忽略,因为它不像业务 key 那么直观。

比如:

  • 慢消费者导致输出缓冲堆积
  • 从库复制链路积压
  • 某类客户端连接方式异常
  • Pub/Sub 或订阅类使用方式把内存顶上去

这时候你如果一头扎进 key 采样,往往半天都看不出为什么“数据没多多少,内存却还是在涨”。

所以第一步只做一件事:把上涨落到正确位置

如果现场已经开始紧张,我通常会先对着几组指标把问题收窄,而不是立刻展开一整套大而全排查。

最先看的其实就几项:

  • used_memory
  • used_memory_rss
  • mem_clients_normal
  • mem_clients_slaves
  • expired_keys
  • evicted_keys

这几项不复杂,但足够把方向拉直。

如果 used_memory 跟着明显上涨

那就先承认:这次更像数据集本身在变大。

这时候重点再回到:

  • key 数量是不是在涨
  • 是否大量 key 没有 TTL
  • TTL 是不是过长
  • 某类集合、列表、Hash 是否在持续膨胀
  • 有没有少数大 key 把内存集中吃掉

也就是说,只有当你确认 used_memory 真在涨时,“去找大 key”“去看 TTL 设计”这些动作才是真正同一条线上。

如果 used_memory 还算平,但 RSS 一直涨

那就别先把矛头指向业务数据了。

这更像:

  • 内存碎片
  • fork 期间额外放大
  • 大量写删后的回收效率问题

这种情况下,现场最容易做错的一件事就是:看见总内存高,就急着清缓存。

但如果业务数据本身没怎么涨,清缓存不一定能把 RSS 按预期打下来,反而可能把命中率和回源链搞坏。

如果客户端相关内存也在同步长

那就得把视线从 key 本身挪开一点。

这类现场更该优先问的是:

  • 有没有慢消费者
  • 有没有连接堆积
  • 主从复制是不是异常拉长了缓冲占用
  • 某类客户端是不是读写方式不对

因为这时内存上涨的主线,可能根本不在业务缓存设计,而在使用方式。

我更在意的,不是“有没有大 key”,而是“旧数据为什么没退下去”

就算最后确认是数据集在涨,我也不太喜欢一上来就把问题收成“大 key 排查”。

因为很多 Redis 内存上涨,根因其实不是某个特别夸张的单 key,而是更普通、也更难被第一眼看出来的问题:

  • key 数量一直在加
  • 过期策略名义上有,实际上退得很慢
  • 某些写路径漏了 TTL
  • 活动、预热、灰度、兜底缓存一层层叠上去,没人再说得清哪些 key 会自然消失

也就是说,真正更值得先问的是:

这些本该是缓存的数据,为什么没有按预期退场?

这个问题一旦问清,很多现场会比“先扫大 key”收得更快。

因为大 key 只是少数对象的问题,旧数据退不掉才更像系统性问题。

一个更接近值班现场的判断顺序

以后如果再碰到 Redis 内存持续上涨,我会更建议按下面这个顺序收:

先分清涨的是数据、RSS,还是客户端相关占用

这是第一刀。没这一步,后面全靠猜。

再确认是不是“新数据一直进,旧数据退不掉”

如果 used_memory 在涨,这一步往往最关键。重点不是先追究某个神秘坏 key,而是先看 key 总量、TTL 分布、过期是否真的在发生。

然后才看是否存在少数大 key 或局部膨胀对象

这一步当然要做,但它更像第二轮收口动作,不该永远抢在最前面。

最后再决定是扩容、清理、调 TTL,还是修使用方式

很多团队把动作顺序做反了:先扩容,先删数据,先清缓存,最后才去理解到底哪段在涨。

这样做有时能暂时止血,但不容易留下真正有用的判断。

为什么这条线比“大而全分型”更重要

因为值班现场没有那么多耐心看一套完整分类学。

真正能救人的,通常只是一个很短的判断线:

  • 先别急着喊大 key
  • 先看涨的是哪一段
  • 再决定问题是在数据、RSS,还是客户端相关内存
  • 再往下做对应动作

这条线看起来简单,但它能避免很多特别常见的误判。

而 Redis 内存问题最怕的,恰好就不是“暂时还没完全定位”,而是“定位方向一开始就错了”。

最后压成一句话

Redis 内存上涨时,最先要避免的不是漏看某个大 key,而是把所有上涨都当成同一种上涨。

先分清涨的是哪一段,再决定往哪条线追,这一步比后面很多技巧都值钱。

因为只有方向对了,你才知道自己是在处理缓存数据、处理内存形态,还是在处理客户端和复制链路。