热点 Key 不扩容也能缓解吗:拆分、复制与本地缓存
来源:17golang原创
时间:2026-10-08 10:10:00 397浏览 收藏
可以缓解,但前提是先弄清热点来自读还是写。Redis Cluster 中一个 Key 只属于一个哈希槽,稳定状态下这个槽由一个主节点负责,所以单纯把集群空闲资源留在旁边,并不会自动分担这个 Key 的压力。我的经验是:写热点优先拆分,读多写少优先复制或本地缓存,读写都很频繁则先改数据模型和访问路径。
不扩容不是“零成本”。拆分会增加聚合开销,复制会增加一致性复杂度,本地缓存会引入失效与内存管理。真正目标是把单点压力换成业务可接受的代价。
官方文档:https://redis.io/docs/latest/
先确认问题真的是热点 Key
热点 Key 的典型现象不是所有分片一起忙,而是某个分片 CPU、命令量或延迟明显高于其他分片。Redis 官方监控文档把热点 Key 定义为被极高频访问的 Key,并指出一个 Key 只属于一个分片,因此它可能把负载集中到单个分片。
排查时先看分片级 CPU、延迟、网络流量和命令分布,再使用热点采样。官方文档提示,redis-cli --hotkeys 依赖 LFU 统计;MONITOR 可能产生较高影响,只适合谨慎、短时、二次确认,不能当成常驻观测手段。
# 先确认实例当前的内存淘汰策略,避免误判 --hotkeys 的可用性 redis-cli CONFIG GET maxmemory-policy # 在具备 LFU 统计的前提下采样热点 Key redis-cli --hotkeys # 检查拆分后的 Key 是否落到不同哈希槽 redis-cli CLUSTER KEYSLOT 'article:42:part:0' redis-cli CLUSTER KEYSLOT 'article:42:part:1'
这里最重要的检查点是:热点是否持续、是否集中在同一个业务对象、读写比例怎样、值多大、更新频率多高。如果只是一次流量尖峰,限流和请求合并可能比改数据模型更合适。
三条路线解决的是不同压力
| 方案 | 更适合 | 主要收益 | 主要代价 |
|---|---|---|---|
| 拆分 Key | 计数、集合、可分区聚合的写热点 | 把写入分散到多个槽和分片 | 读取需要聚合,跨槽操作受限 |
| 复制 Key | 读多写少、允许短暂最终一致 | 把读取路由到多个物理 Key | 更新扇出、版本切换和过期管理更复杂 |
| 本地缓存 | 高频读取、变化不频繁的小对象 | 直接减少网络请求与 Redis 回源 | 失效通知、断线清空和进程内存成本 |
我不建议先拍脑袋选择“最强”的方案。把热点类型与代价对上,通常比一次性叠加三层缓存更容易上线,也更容易回滚。

写热点先拆成可合并的分片 Key
如果热点是全局计数器、点赞数、曝光数或可分区集合,可以把一个逻辑对象映射成多个物理 Key。请求依据用户 ID、请求 ID 或业务分区选择一个桶,例如 article:42:part:0 到 article:42:part:63。读取总数时再聚合这些桶,或者异步把桶汇总到一个只读快照。
Redis Cluster 有 16384 个哈希槽,Key 默认按完整键名计算槽位。拆分时不要给所有桶使用相同的哈希标签,否则花括号中的相同内容会强制它们回到同一个槽,等于把热点重新集中起来。跨槽的多 Key 命令、事务和 Lua 脚本也有约束,所以聚合通常要放在应用层,或由后台任务生成快照。
package hotkey
import (
"fmt"
"hash/fnv"
)
// BucketKey 根据稳定标识选择写入桶,避免同一请求重复路由到不同桶。
func BucketKey(base, stableID string, buckets uint32) string {
h := fnv.New32a()
_, _ = h.Write([]byte(stableID))
// 键名不使用相同哈希标签,让不同桶有机会分布到不同槽。
bucket := h.Sum32() % buckets
return fmt.Sprintf("%s:part:%02d", base, bucket)
}
拆分的验收不是“生成了 64 个 Key”,而是这些 Key 的槽位和请求量确实分散,同时聚合延迟仍在预算内。桶太少无法摊平,桶太多则会放大读取、扫描和过期成本。
读多写少可以复制,但要接受一致性成本
对热门配置、商品基础信息或公共字典,可以维护多个内容相同的物理 Key,例如 product:42:copy:0 到 product:42:copy:7,请求按稳定散列读取其中一个。与写拆分类似,这些副本不能被相同哈希标签重新绑到同一个槽。
复制不是 Redis 自动替你完成的数据一致性协议。更新时需要应用或发布任务写入全部副本,并用版本号、校验值或独立的发布状态判断是否完成。如果业务不能接受几秒钟的版本不一致,就不要把应用级副本当成默认方案;强一致读取应保留单一权威路径,或使用经过验证的读副本能力并明确陈旧读取是否可接受。
我更喜欢“新版本 Key + 旧版本延迟过期”的方式:先写一组带版本的新副本,确认数量和版本一致,再切换读取版本;出现异常时把读取版本切回去。它牺牲一些内存,却比原地逐个覆盖更容易回退。
本地缓存最省 Redis 请求,也最容易藏住陈旧数据
Redis 官方的 server-assisted client-side caching 使用 Tracking:服务器记录连接读过哪些 Key,当 Key 被修改、淘汰或过期时发送失效消息,客户端收到后移除本地副本。这样应用命中本地缓存时不再访问 Redis,网络和服务器负载都会下降。
它尤其适合“读得多、改得少”的小对象。对持续 INCR 的计数器、频繁变化的排行榜或大对象,本地缓存可能被失效消息反复击穿,管理成本反而高于收益。官方文档还要求连接丢失时清空本地缓存,避免应用在失去失效通道后继续返回旧数据。
- 只缓存明确的热点前缀或白名单,不要默认把所有读取都塞进进程内存。
- 设置本地容量上限、短 TTL 与随机抖动,避免大量进程同时回源。
- 把缓存友好和高频更新的数据分开连接,减少无效的跟踪与失效消息。
- 监控断线清空次数、失效消息量、本地命中率和 Redis 回源比例。
推荐的上线顺序与回滚开关
我通常先加一个只读的热点观察面板,再为候选 Key 做灰度开关。第一阶段只记录命中和路由结果;第二阶段让少量请求使用拆分、副本或本地缓存;第三阶段才扩大比例。每个方案都要保留直接访问原 Key 的回退路径。
上线后至少同时观察四组指标:热点分片 CPU 是否下降、P95/P99 延迟是否改善、新增聚合或失效开销有多大、错误和陈旧读取是否增加。只看平均延迟很容易掩盖单分片仍在抖动的事实。

容易踩的四个坑
- 拆了 Key 却保留相同哈希标签。物理 Key 变多了,槽位仍然相同,单分片压力没有改变。
- 复制后没有版本字段。无法判断读到的是哪一批数据,也无法安全回滚。
- 本地缓存只设 TTL,不处理失效通道断线。连接异常后可能长时间返回旧值。
- 只盯 Redis 命中率。命中率高不代表尾延迟、分片倾斜和一致性错误都已改善。
快速选择清单
- 高频累加、允许最终聚合:优先拆分 Key。
- 内容稳定、读远多于写、允许短暂不一致:考虑多个副本 Key。
- 对象小、读频繁、客户端支持可靠失效:优先本地缓存。
- 写入频繁且要求强一致:不要先复制,优先重构数据模型、限流或合并请求。
- 单分片仍有真实容量瓶颈:这些方法只能缓解,最终仍可能需要扩容或重新分片。
所以,热点 Key 在不扩容时确实有治理空间,但要把“分散压力”和“复制数据”看成不同工具。先用最小改动验证一个方向,再依据分片 CPU、尾延迟和一致性指标决定是否保留,通常比一次性堆满缓存层更稳。
常见问题
把一个大 Hash 拆成多个 Hash 一定有效吗?
不一定。只有拆分后的键落到不同槽并且请求也被分散,才可能降低单分片压力;如果仍使用相同哈希标签,效果很有限。
可以直接从 Redis Cluster 副本读吗?
集群规范允许在接受陈旧数据的场景使用副本扩展读取,但客户端、路由与一致性语义必须明确。它与应用自行复制多个 Key 不是同一方案。
本地缓存失效消息丢了怎么办?
连接或失效通道异常时应清空相关本地缓存并回源 Redis,恢复后再重新建立跟踪,不能继续信任旧副本。
热点消失后需要恢复单 Key 吗?
不必立刻恢复。先比较聚合成本和运维复杂度;如果热点已经消失且拆分长期增加维护成本,可以通过灰度开关回到单 Key。
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习