Redis 热 key 问题怎么发现和处理?从一次分片负载失衡查到首页活动配置说起
热 key 最容易被误查成“Redis 集群容量不够”。这次事故里,先变坏的是一个分片的 CPU 和网络,而不是整组命中率。真正有用的排查,是先证明压力有没有被压在少数节点、少数 key 上,再决定本地缓存还是拆热点。
热 key 这个词大家都熟,但真到现场,很多人还是会先把它当成“Redis 不够扛”的容量问题。其实两者差别很大。集群容量不够时,通常是整体一起吃紧;热 key 更常见的样子,是只有某个分片先红,别的节点还挺安静。
我印象里最典型的一次,是首页活动会场切主推位后,应用接口 p99 开始抖。最先被发现的不是命中率下滑,也不是数据库回源,而是 Redis 集群里 17 号分片 CPU 冲到 92%,网络出流量几乎是别的节点三倍。
这一下就把方向拉窄了:先别急着谈扩容,先看为什么只有这个分片挨打。
这次事故的首证据,是节点负载失衡
当时把 Redis 集群节点按 1 分钟粒度拉出来,对比很夸张:
| 节点 | QPS | CPU | 出流量 |
|---|---|---|---|
| shard-15 | 8.1w | 41% | 82MB/s |
| shard-16 | 7.7w | 38% | 76MB/s |
| shard-17 | 19.4w | 92% | 211MB/s |
| shard-18 | 8.5w | 44% | 87MB/s |
这种图一出来,其实就别再从“整体命中率正常不正常”入手了。因为问题已经不是缓存有没有命中,而是请求分布是不是严重失衡。
继续往 shard-17 上打 MONITOR 样本和应用侧埋点,发现绝大多数请求都在读同一类 key:
activity:homepage:slot:main
也就是说,真正被打热的不是商品详情、不是库存,而是首页主会场的活动配置。
为什么这次更像热 key,而不是数据库回源或者大面积失效
因为几个现场特征特别典型。
第一,Redis 整体命中率并没掉,数据库回源也没显著升高。这说明缓存吸收能力没有整体漏掉。
第二,只有一个分片异常忙,说明压力是被某些固定 key 压过去的,而不是全局流量普遍增长。
第三,接口慢主要集中在首页渲染链路,别的读链路变化不大。要是是雪崩或统一过期,不会长成这么局部。
这些特征拼在一起,基本就把问题钉成了“少数 key 把分片打偏”。
根因其实很朴素:一个公共配置被所有请求顺手读了一次
后来翻代码才发现,首页会场改版以后,新版页面每次渲染都会强制拉取主活动位配置;这个配置更新频率很低,但被挂在了所有首页请求的同步路径上。
换句话说,系统把一个本来适合本地兜底的公共数据,变成了每个请求都要去 Redis 拿一次的同步依赖。
只要首页流量一起上来,这个 key 就天然会变热。而它又恰好落在 shard-17,于是负载失衡就被放大得特别明显。
为什么一开始扩容并没有解决问题
当时也试过给 Redis 集群临时加节点,结果效果很差。原因其实不复杂:
- 集群规模是变大了
- 但这个 key 的 hash 路由没变
- 流量还是继续压在原来那个槽位和节点上
所以热 key 跟“整体容量不够”最大的区别就在这里:你不改访问形态,只扩机器,热点通常还会是热点。
这次真正生效的动作,是把热点从 Redis 前挪开
最后做了两步,效果立刻就出来了:
- 在应用实例里给
activity:homepage:slot:main加 15 秒本地缓存 - 配置变更时走消息广播,主动刷新本地副本
动作上线后,回落顺序也很清楚:
- 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 集群故障的现场,其实都会收敛成一个更具体、也更好下手的问题。