Java

Redis 分片后热点仍集中,问题更可能出在哪一层?

Redis 分片能摊开平均压力,却不会顺手打散爆款对象、集中过期和回源窗口。分片做完热点还扎堆时,更该回头检查 key 设计、访问形态、本地缓存和失效节奏,而不是一上来都算到槽位分布头上。

  • Redis
  • 分片
  • 热key
  • 缓存
  • 性能排查
18 分钟阅读

很多 Redis 热点治理会先走到分片这一步,这个方向没错,但分片上线后如果现场还是这样:

  • 少数节点在高峰时持续偏热
  • 热门接口 RT 还是一阵一阵抖
  • 热 key 没消失,只是从单机问题变成集群里的局部问题
  • 数据库回源、连接池等待也开始跟着升

团队很容易立刻去盯槽位、迁移和 hash 分布。可真实线上里,更常见的情况是:平均承载已经摊开了,热点语义却一点没变。请求还是围着那几十个对象打,过期时间还是扎堆,本地缓存还是没兜住,最后热点当然还会继续集中。

所以这篇文章想讲清楚的不是“分片有没有价值”,而是另一个更常把人带偏的问题:

Redis 已经分片,为什么热点还是没散开?这时更该怀疑分片实现,还是先回到 key 设计、访问模式和失效策略看热点究竟卡在哪一层?

热点还在时,先分清落点

分片做完以后还在偏热,很多团队会立刻把注意力放到槽位和迁移细节上。但比追着实现细节更重要的,是把现场信号分粗:热点到底卡在单 key、对象族群,还是失效窗口。

你现在看到的现象建议先把排查落到哪一层为什么
已经做了 Redis 分片,但热点仍集中在少数节点或少数对象上看完本文再定下一步这里先帮你判断热点究竟停在 key、对象族群还是失效窗口
你还没确认是不是热 key、热点族群或失效窗口转去热 key 治理页热点类型分清,后面的动作才不会跑偏
你在处理的是大面积回源、TTL 集中过期转去回源放大页或 TTL 页这时更像 miss 窗口把后半段等待链一起带起来了
你想讨论 Redis 集群部署实现和迁槽细节把本文当成前置判断先确认问题是不是确实落在分片层,再深入实现细节

这种现场为什么别急着查分片实现

如果分片已经做完,但热点还持续扎堆,就可以直接按下面这个顺序往下收。

它想纠正的误判是:分片能摊开平均负载,却不会自动改写热点语义。所以当热点还在时,更值得回头看 业务对象、key 设计、本地缓存和失效策略,确认热点有没有真的被打散,而不是直接把锅扣到集群实现上。

先把热点收成 5 个判断

  1. 把热点分成点状热点、热点族群和窗口型热点三类。
  2. 再看热点是不是本来就高度绑定少数业务对象,而不是 hash 槽不均。
  3. 再看热点 key 有没有本地缓存、边缘缓存或失效节奏保护。
  4. 如果数据库回源、连接池和接口 RT 也在变差,把后半段等待链一起带上。
  5. 只有当前几层都解释不住,再优先查分片实现本身。

一、先纠正一个预期:分片解决的是“平均压力”,不是“热点语义”

这是理解这类问题的起点。

Redis 分片通常能直接解决的是:

  • 总容量不够
  • 总 QPS 太高
  • 单机内存和网络带宽到瓶颈
  • 平均请求分布需要更大的承载面

但它默认没替你做的事包括:

  • 把单个热 key 变成多个 key
  • 把业务上的超级热点对象打散
  • 把热点失效时间错开
  • 把每次请求都打 Redis 的模式自动改成本地兜底

也就是说,分片更多是在回答:

当请求已经足够分散时,系统能不能横向承接更多平均负载?

它没有自动回答:

为什么请求会过度集中到少数对象上?

所以如果你把“分片后热点应该自然消失”当作默认预期,后面看到热点还在,就会觉得是系统出了反直觉的毛病。其实不是,预期本身就错了。

二、分片后热点仍集中,最常见的 4 类真实原因

1. 热点本来就是单 key 级别,分片只是在扩大背景,不会打散这个 key

这是最常见的一类。

比如:

  • 爆款商品详情
  • 首页活动配置
  • 排行榜结果
  • 某个全局开关
  • 某个超级热门用户主页

这类热点的本质是:

  • 所有请求都在打同一个 key
  • 分片再多,这个 key 还是只能落在某一个节点或某一个槽位

所以你会看到:

  • 整个集群总容量更大了
  • 但单个热 key 仍然把局部节点打偏
  • 背景流量被分散后,这个节点反而显得更突出了

这类问题不是分片失败,而是热点仍然停留在 key 级别。

2. 热点不是单 key,而是单一业务对象族群仍然过于集中

有时热点看起来不是一个 key,而是一小批 key。

例如:

  • 活动页上的几十个爆款商品
  • 某个租户下的热门资源集合
  • 某个榜单页不断刷新的 top N 数据
  • 同一地区、同一频道、同一业务入口下的热点内容

这时即使分片后:

  • key 数量变多了
  • 总体分布也许比以前好一点

但如果这些 key:

  • 命名规则相近
  • 请求时间窗高度重合
  • 失效时间也相近

那么热点仍然会集中在少数节点、少数时段。

这里的问题本质上在业务流量结构,不在“有没有分片”。

3. 热点并不是读热点本身,而是“失效后的回源热点”

这类特别容易误判。

表面上你看到的是:

  • 某些 Redis 节点在高峰时特别忙
  • 热门接口 RT 也在抖
  • 但更严重的时候数据库回源和连接池也一起变差

继续查会发现:

  • 热点 key 失效窗口太集中
  • 本地缓存没有兜住
  • Redis miss 以后大量请求一起回源
  • 数据库回填慢,又把下一波热点继续放大

这时真正的问题不只是“Redis 热点还在”,而是:

分片没有解决热点失效和重建模式,热点在 miss 后直接转成了数据库等待链。

4. 热点实际上被应用实例本地行为重新集中了一次

很多团队只看 Redis 节点,不看应用层访问模式。

但真实项目里,热点还可能在应用侧被重新放大:

  • 本地缓存没有做,所有实例都直接打 Redis
  • 本地缓存 TTL 太短,实例群一起回 Redis
  • 某批实例因为发布、重启、缓存预热缺失,先于其他实例 miss
  • 某些流量入口把更多请求集中路由到少数实例,再集中打某批 Redis key

这种场景下,你看到的是 Redis 热点,但根子有一部分在应用层的访问形态。

三、先别一上来查 hash 槽:先判断热点到底是“点状”还是“簇状”

如果以后再遇到“分片后热点还在”,第一步我更建议先问:

热点是集中在一个 key,还是一簇 key,还是一整类失效窗口?

1. 点状热点

信号通常是:

  • 少数 key 命中次数远高于其他 key
  • 节点负载不均明显,但热点对象很容易数出来
  • 热门接口与几个固定业务对象高度相关

这类更像典型热 key 问题。

2. 簇状热点

信号通常是:

  • 不是单 key,而是一批 key 一起热
  • 同一个业务页、同一个榜单、同一个活动入口相关 key 同时被打
  • 节点偏热与某类业务前缀或某批对象集合对应明显

这类更像业务对象族群和流量入口问题。

3. 窗口型热点

信号通常是:

  • 某些时间点 Redis 和数据库一起抖
  • 热点更依赖失效时间、预热时间、发布窗口
  • 命中率与 miss QPS 在短时间窗口内尖刺很明显

这类更像 TTL、预热、逻辑过期和重建策略问题。

只有先分清这三类,后面才知道该查哪一层。

四、排查顺序别倒过来:先业务热点,再失效策略,最后分片实现

很多人默认会先查:

  • 槽位分布
  • 节点数
  • hash 算法
  • 集群扩容是否做均衡

这些方向不是不该查,但通常不该排在第一位。更贴近现场的顺序往往是下面这样。

1. 热点对象是不是本来就高度集中

重点问:

  • 慢接口是不是高度集中在少数业务对象
  • 热点 key 是否容易枚举出来
  • 最近活动、推荐、入口流量是否刚把一批对象打成超热点
  • 热点对象在分片前后是否其实没变,只是背景流量变了

如果答案是“是”,问题更可能在业务热点,而不是集群实现。

2. 再看热点 key 是否有本地缓存或边缘兜底

如果每个请求都要直打 Redis:

  • 热点 key 很容易持续发热
  • 分片只能降低别的 key 对它的干扰
  • 无法降低它自身被访问的频率

这里特别要看:

  • 热点配置、榜单、公共字典是否可以本地缓存
  • 应用实例有没有短 TTL 本地兜底
  • 逻辑过期或后台刷新有没有做

很多热点问题真正缺的不是更多 Redis 节点,而是少打一层 Redis。

3. 再看热点失效窗口是否集中

重点看:

  • 热点 key 是否在同一时间段一起过期
  • 预热是否做了,但热点集合和真实流量并不匹配
  • 某批实例重启后是否让热 key 回源集中发生
  • 热点 key 是否采用了互斥重建、逻辑过期或异步刷新

如果这里有问题,分片后热点依然会在某几个窗口里爆出来。

4. 再看 key 设计有没有把“热点族群”放在同一类模式里

这一步不是说 Redis 哈希槽一定有问题,而是要看:

  • 业务键是否天然集中在一批对象上
  • 某类 key 是否命名前缀不同,但语义上总被一起访问
  • 一批热门对象是否总在同一页面、同一接口批量读取

如果请求总是成组访问同一批 key,热点就不再是单 key 问题,而是业务读模式问题。

5. 最后再查分片实现本身

到了这一步,再看:

  • 槽位是否均匀
  • 节点是否有异常偏载
  • 扩容迁移是否残留问题
  • 客户端路由、pipeline、连接管理是否有局部问题

这一步当然重要,但它更适合作为排除法的后段,而不是默认起点。

五、为什么很多“分片后热点仍集中”,最后会演变成数据库和接口一起慢

这类问题最麻烦的,不是 Redis 节点偏热本身,而是热点一旦失效、抖动或超时,后面很容易顺着缓存故障链继续传导:

Redis 热点集中 -> 热点 key miss 或超时 -> 大量请求回源数据库 -> 回填变慢、连接池变紧 -> 线程池和接口 RT 一起变差

所以现场常常不是只有:

  • 某个 Redis 节点忙

而是同时出现:

  • 热门接口抖
  • 数据库回源升
  • 连接池 pending 上来
  • 上游重试继续放大

如果走到这一步,说明真正该看的已经不只是分片,而是整条“热 key -> miss -> 回源 -> 等待”的链路。

六、一个典型案例:为什么商品详情做了 Redis 分片,爆款还是把系统拖抖了

假设某个商品详情系统做了下面这些治理:

  • Redis 从单机切成分片集群
  • 热门商品详情都走缓存
  • 总体 QPS 承载能力明显提升

平时看起来都不错,但大促一来仍然出现:

  • 某些节点明显更热
  • 爆款详情接口 P99 明显升高
  • 数据库商品详情查询量也一起抬头

第一眼容易怎么判断

很多人会说:

  • 分片没打散
  • 扩容还不够
  • Redis 集群某些节点太弱

继续往下查

会发现:

  • 真正超热点的商品就那几十个
  • 这些商品详情 key 虽然分散在不同槽位,但每个 key 都足够热
  • 应用层没有本地缓存,所有请求都直接打 Redis
  • 热点 key TTL 比较短,且一批爆款的过期时间很接近
  • 一旦有几组 key 同时进入重建窗口,数据库回填也会被拉高

结论

这时问题并不是“分片无效”,而是:

  • 分片只解决了平均承载
  • 没解决超级热点 key 本身
  • 没解决热点失效窗口
  • 没解决本地访问模式

这个案例很典型,也最能说明为什么这类问题不该默认从分片实现层开始查。

七、关键误判:这类问题最容易在哪些地方走偏

误判 1:分片后热点还在,就说明分片方案失败

不一定。

更常见的是热点语义根本没变,分片本来就没负责解决这部分。

误判 2:看到节点负载不均,就立刻扩容

如果是少数热 key 或热点失效窗口问题,扩容最多降低背景噪声,不会根治热点。

误判 3:只查 Redis,不看数据库回源和接口等待

很多热点问题真正伤系统的,是 miss 后半段的等待链,而不是节点偏热截图本身。

误判 4:把所有热点都归因于 hash 分布

业务对象集中、接口入口集中、失效策略集中,本身就足以制造热点,不需要 hash 层先出问题。

误判 5:有了 Redis 集群,就不再考虑本地缓存和逻辑过期

恰恰相反,越是超热点对象,越需要多层吸收,而不是把所有请求都直打 Redis。

八、FAQ:分片已经做了,现场通常还会追问什么

1. 分片是不是只能解决平均负载,不能解决热 key?

基本可以这么理解。分片更擅长横向扩平均承载,不会自动把单个热 key 打散。

2. 这类问题默认先查集群,还是先查业务热点?

默认更建议先查业务热点、key 访问结构和失效窗口,再排分片实现本身。因为前者更高频,也更容易被忽略。

3. 热点 key 做本地缓存,会不会带来一致性问题?

会带来权衡,但对配置、榜单、热点详情这类可接受短时间最终一致的数据,通常比持续直打 Redis 更稳。

4. 分片后节点负载不均一定是坏事吗?

不一定。

如果业务热点本来就偏斜,局部节点更忙并不奇怪。关键要看这种偏斜是否已经把 RT、miss 和数据库回源一起拖坏。

九、接下来通常会顺手查这几件事

如果已经确认做过分片,但热点还是砸在少数节点上,后面一般不会只盯着 hash 槽看,而是把下面几条线一起补齐。

十、最后总结:分片把路修宽了,不等于热点自己会散

这类现场最容易误判的地方,是把“已经分片”直接等同于“热点应该平均”。可分片主要解决的是平均承载,不会替你改写爆款对象、集中过期和本地未命中的访问结构。

所以更有用的收口方式是:先看热点到底是单 key、对象族群还是失效窗口,再分别回到 key 设计、本地缓存、TTL 和回源重建策略;只有这些都解释不住,才把怀疑重点压到分片实现本身。这样看问题,后面的治理动作才会更具体,也更容易真正把热点压下去。