Java

Redis 热 key 问题怎么发现和处理?从一次分片负载失衡查到首页活动配置说起

热 key 最容易被误查成“Redis 集群容量不够”。这次事故里,先变坏的是一个分片的 CPU 和网络,而不是整组命中率。真正有用的排查,是先证明压力有没有被压在少数节点、少数 key 上,再决定本地缓存还是拆热点。

  • Redis
  • 热key
  • 缓存
  • 性能优化
9 分钟阅读

热 key 这个词大家都熟,但真到现场,很多人还是会先把它当成“Redis 不够扛”的容量问题。其实两者差别很大。集群容量不够时,通常是整体一起吃紧;热 key 更常见的样子,是只有某个分片先红,别的节点还挺安静。

我印象里最典型的一次,是首页活动会场切主推位后,应用接口 p99 开始抖。最先被发现的不是命中率下滑,也不是数据库回源,而是 Redis 集群里 17 号分片 CPU 冲到 92%,网络出流量几乎是别的节点三倍。

这一下就把方向拉窄了:先别急着谈扩容,先看为什么只有这个分片挨打。

这次事故的首证据,是节点负载失衡

当时把 Redis 集群节点按 1 分钟粒度拉出来,对比很夸张:

节点QPSCPU出流量
shard-158.1w41%82MB/s
shard-167.7w38%76MB/s
shard-1719.4w92%211MB/s
shard-188.5w44%87MB/s

这种图一出来,其实就别再从“整体命中率正常不正常”入手了。因为问题已经不是缓存有没有命中,而是请求分布是不是严重失衡。

继续往 shard-17 上打 MONITOR 样本和应用侧埋点,发现绝大多数请求都在读同一类 key:

activity:homepage:slot:main

也就是说,真正被打热的不是商品详情、不是库存,而是首页主会场的活动配置。

为什么这次更像热 key,而不是数据库回源或者大面积失效

因为几个现场特征特别典型。

第一,Redis 整体命中率并没掉,数据库回源也没显著升高。这说明缓存吸收能力没有整体漏掉。

第二,只有一个分片异常忙,说明压力是被某些固定 key 压过去的,而不是全局流量普遍增长。

第三,接口慢主要集中在首页渲染链路,别的读链路变化不大。要是是雪崩或统一过期,不会长成这么局部。

这些特征拼在一起,基本就把问题钉成了“少数 key 把分片打偏”。

根因其实很朴素:一个公共配置被所有请求顺手读了一次

后来翻代码才发现,首页会场改版以后,新版页面每次渲染都会强制拉取主活动位配置;这个配置更新频率很低,但被挂在了所有首页请求的同步路径上。

换句话说,系统把一个本来适合本地兜底的公共数据,变成了每个请求都要去 Redis 拿一次的同步依赖。

只要首页流量一起上来,这个 key 就天然会变热。而它又恰好落在 shard-17,于是负载失衡就被放大得特别明显。

为什么一开始扩容并没有解决问题

当时也试过给 Redis 集群临时加节点,结果效果很差。原因其实不复杂:

  • 集群规模是变大了
  • 但这个 key 的 hash 路由没变
  • 流量还是继续压在原来那个槽位和节点上

所以热 key 跟“整体容量不够”最大的区别就在这里:你不改访问形态,只扩机器,热点通常还会是热点。

这次真正生效的动作,是把热点从 Redis 前挪开

最后做了两步,效果立刻就出来了:

  1. 在应用实例里给 activity:homepage:slot:main 加 15 秒本地缓存
  2. 配置变更时走消息广播,主动刷新本地副本

动作上线后,回落顺序也很清楚:

  • shard-17 的 QPS 和 CPU 先掉
  • 首页接口 p99 在 2 分钟内从 780ms 回到 210ms
  • 其他节点几乎没什么变化

这其实特别说明问题:它不是“Redis 整组扛不住”,而是某个公共热点不该每次都去 Redis 拿。

这类热 key 最容易被忽略的地方

最容易被忽略的,不是热 key 本身,而是它常常披着“公共配置”“轻量字典”“一个很小的 JSON”这种外衣。数据体积不大、查询逻辑也不复杂,于是大家本能觉得没什么风险。

可热 key 的危险从来不只看单次查询多贵,而看是不是所有请求都在打同一个点。哪怕只是一段很小的配置,只要读得足够集中,也能把一个分片压歪。

什么时候该优先按热 key 去收敛

如果你看到的是下面这种现场,很适合先按热 key 处理:

  • Redis 集群里只有少数节点明显更忙
  • 数据库回源没有同步大幅上升
  • 某条业务链路抖得厉害,但别的接口相对正常
  • Top key / key 前缀访问量高度集中

这种形状跟 TTL 集中过期、命中率整体下降、数据库等待链放大都不太一样。它更像是流量分布失衡,而不是缓存整体吸收失败。

回到这次事故想留下的一句话

Redis 热 key 问题,真正该先看的不是“要不要扩容”,而是压力是不是被压在少数节点、少数 key 上。只要这一步成立,后面的处理就不该再是泛泛的容量思路,而是去改访问路径:能不能本地兜一层,能不能拆热点,能不能让公共数据别再每次都跨网络取。

这一步想明白,很多看起来像 Redis 集群故障的现场,其实都会收敛成一个更具体、也更好下手的问题。