主题
热点 Key 问题
一句话:热点 Key 是访问频率远高于其他 key 的单点,问题不在体积,而在它把单节点 CPU、带宽或连接数打满;处置靠本地缓存、副本打散和分片,单纯加 Redis 节点没用。
适用版本:Redis 7.x / 8.x。--hotkeys 与 OBJECT FREQ 需要 LFU 淘汰策略。
为什么需要
Redis Cluster 按 key 分片,一个 key 只能落在一个 slot、一个主节点上。某个商品或明星微博突然爆火,所有请求都打到同一个节点;节点 CPU 100% 后,同一节点上的其他 key 也一起变慢,客户端超时重试又把流量放大。热点 key 和大 key 经常叠加,但根因不同:前者是访问频率,后者是体积。
核心机制
影响链路
热点 Key 的危害是一条正反馈链:客户端重试会把单节点过载放大成集群级排队。
发现手段
| 方法 | 做法 | 局限 |
|---|---|---|
redis-cli --hotkeys | 全库 SCAN,按 LFU 计数器排序 | 必须先把 maxmemory-policy 设为 LFU 类;全库扫描有开销;结果依赖采样精度 |
OBJECT FREQ key | 直接读某个 key 的 LFU 计数 | 同样要求 LFU;只能逐个查 |
| 客户端/业务埋点 | 应用层统计 key 访问次数,阈值上报 | 最精准,但需要改代码 |
| 代理层统计 | 在 Twemproxy/Codis/Envoy 等代理侧按 key 统计 | 依赖代理能力,直连架构没有 |
| 分片级监控 | 对比 Cluster 各主节点的 CPU、QPS、带宽 | 只能定位到节点,不能直接定位 key |
MONITOR | 实时打印所有命令 | 开销极大,生产禁用 |
SLOWLOG | 慢查询里找高频 key | 间接指标,热点 key 不一定慢 |
--hotkeys 的用法:
bash
redis-cli CONFIG SET maxmemory-policy allkeys-lfu
redis-cli --hotkeys
redis-cli OBJECT FREQ product:1解决方案
本地缓存(首选):热点数据放在应用进程内(Caffeine/Guava),请求不出网络。TTL 设 1–5 秒,允许短暂不一致;多实例之间用 Redis Pub/Sub 或配置中心广播失效。注意本地缓存会占应用堆内存,热点 key 的 value 不能太大。
副本打散:把一个逻辑 key 复制成 N 份,例如 product:1:01 到 product:1:04,客户端随机读一份。写入时要更新所有副本(Pipeline/Lua),或者接受短 TTL 内的不一致。副本数 2–4 通常够用,代价是 N 倍内存和 N 倍写放大。
分片:对可累加的数据(计数器、库存)拆成 N 个子 key,读时汇总;对榜单类数据按维度拆。分片会改变业务读写逻辑,适合能接受最终一致的场景。
读写分离:热点读分发到副本,写仍走主节点;代价是复制延迟,适合一致性要求不高的读。
限流与降级:极端热点下,宁可对部分请求返回降级内容,也不要让单节点拖垮整个集群。
最小上手
bash
redis-cli
# 开启 LFU 后才能用 --hotkeys / OBJECT FREQ
CONFIG SET maxmemory-policy allkeys-lfu
CONFIG SET lfu-log-factor 10
redis-cli --hotkeys
redis-cli OBJECT FREQ product:1
# 对比各节点负载(Cluster)
redis-cli -c -p 7000 CLUSTER NODES
INFO commandstats生产动作
- 先确认是热点而不是大 Key:
MEMORY USAGE看体积,OBJECT FREQ/--hotkeys看频率;两者叠加时先拆大 Key。 --hotkeys在低峰跑:它要遍历整个 keyspace,生产高峰执行会加重负载;优先用客户端埋点或代理统计。- 本地缓存 TTL 1–5 秒:超过这个窗口,一致性问题会变得难以解释;失效通知用 Pub/Sub 时要注意消息丢失后的兜底 TTL。
- 副本要落在不同节点:Cluster 下不要给副本 key 加同一个 hash tag,否则又回到同一个 slot;写入用 Pipeline 批量更新。
- 热点 key 提前预热:活动/秒杀前把数据写入本地缓存和 Redis,避免开场瞬间集中回源。
- 监控单节点倾斜:某主节点 CPU/带宽长期是其他节点的 2 倍以上,就要查热点 key。
边界
--hotkeys不是实时监控:它依赖 LFU 计数器和全库扫描,只能做事后/周期性发现,不能替代业务埋点。- LFU 计数会衰减:
lfu-decay-time默认 1 分钟,冷却下来的热点会降权;短时突发可能来不及体现。 - 本地缓存有一致性窗口:多实例各自缓存,更新后最长要等 TTL 或广播到达。
- 副本打散有写放大:N 个副本就要写 N 次,内存也变 N 倍;更新失败会出现副本间不一致。
- 分片改变语义:汇总、排序、事务都要重写,不是所有热点都能拆。
- 加节点不解决单 key 热点:一个 key 只属于一个 slot,扩容不会自动把它的流量分走。
讲清楚
热点 Key 的本质是访问倾斜:一个 key 的 QPS 远高于其他 key,而它只能落在一个节点上,于是这个节点的 CPU、带宽先到顶,同节点的其他请求跟着排队。它和大 Key 经常一起出现,但大 Key 是体积问题,热点是频率问题。
发现靠 LFU 的
--hotkeys、OBJECT FREQ,或者更准的业务埋点和代理统计;解决靠本地缓存扛住大部分读,副本打散分摊单节点压力,能拆的数据再分片。加机器没用,因为一个 key 不会因为集群变大就自动分到多个节点。