什么是热点 Key 问题,如何解决?
结论:热点 Key 指短时间内被极高频访问的单个 key(如秒杀商品、明星微博、爆款库存)。它的危害不是"数据大",而是"流量集中":单分片 CPU 打满、网络带宽被吃光、集群其他节点却空闲。解决思路从便宜到贵依次是:本地缓存 → 读副本 → key 打散 → 限流,且必须先解决"怎么发现热点"。
一、热点 Key vs 大 Key
| 维度 | 热点 Key | 大 Key |
|---|
| 判定依据 | QPS 集中度(如单 key 占实例 10% 以上) | value 体积或集合元素数 |
| 直接后果 | 单节点/单分片 CPU 与带宽打满 | 单次操作变慢、阻塞主线程 |
| 集群表现 | 数据倾斜、单槽过热 | 单槽内存倾斜、迁移慢 |
| 主要解法 | 本地缓存、副本分摊、key 散列、限流 | 拆分、压缩、分页访问、UNLINK |
| 共同点 | 都会让"整实例抖动",而不是只影响某接口 | 同左 |
二、怎么发现热点
| 手段 | 原理 | 代价 |
|---|
redis-cli --hotkeys | 依赖 LFU 策略,按 OBJECT FREQ 排序 | 必须开启 allkeys-lfu,且是采样 |
MONITOR | 打印所有命令 | 极高开销,仅用于短时定位 |
| 代理层统计(如 Codis/Twemproxy) | 在代理处按 key 计数 | 需要额外组件 |
| 客户端埋点 | 应用侧按 key 上报 QPS | 最准确,需改代码 |
| 业务预判 | 大促清单、热搜榜单 | 零成本,但可能漏 |
# 前提:maxmemory-policy 为 allkeys-lfu / volatile-lfu
redis-cli --hotkeys
# 输出示例(按 freq 排序):
# [00.00%] Hot key 'item:10086' found so far with counter 4291
# 短时抓包式定位(务必限定时间,生产慎用)
timeout 5 redis-cli MONITOR | grep -o '"item:[0-9]*"' | sort | uniq -c | sort -nr | head
三、四级解法
┌──────────────────────────────┐
秒杀请求 100 万/s → │ L1 本地缓存(Caffeine) │ 挡住 90%+
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ L2 Redis 读副本(主从/多副本) │ 分摊读流量
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ key 打散:item:10086:{0..15} │ 把 1 个热 key 变 16 个
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ 限流 + 队列削峰 │ 保护后端与 DB
└──────────────────────────────┘
- 本地缓存:热点数据放 JVM 内,QPS 再高也只打本地内存;代价是多实例不一致,需要短 TTL 或广播失效。
- 读副本:一主多从,把读请求分散到从库;注意主从延迟,秒杀库存这类强一致读不能走从库。
- key 打散:把
stock:10086 拆成 stock:10086:0..N,读时随机选一个再求和;写时要么全写,要么用"分桶预扣 + 异步汇总"。适合"读多写少且允许近似"的场景。
- 限流与削峰:在网关按 key 维度限流,超出部分排队或快速失败,保证系统不被热点拖垮。
// 热 key 打散:把一份计数拆成 N 份,读时聚合
int buckets = 16;
int idx = ThreadLocalRandom.current().nextInt(buckets);
redis.opsForValue().increment("stock:10086:" + idx); // 写分散
long total = 0;
for (int i = 0; i < buckets; i++) { // 读聚合,O(N) 但 N 很小
String v = redis.opsForValue().get("stock:10086:" + i);
total += v == null ? 0 : Long.parseLong(v);
}
四、常见追问
- 问:热点 Key 会不会导致缓存击穿?答:会叠加。热 key 一旦过期,海量请求同时 miss 就是击穿;所以热 key 通常配"逻辑过期 + 异步重建"或干脆不设 TTL。
- 问:打散后一致性怎么保证?答:读侧接受"分桶求和"的近似或最终一致;若必须精确(如库存),改用"单 key + 本地缓存扣减 + 异步落库"或直接上专用库存服务。
- 问:Cluster 下热 key 为什么更严重?答:一个 key 只能落一个槽、一个节点,无法像 Kafka 分区那样水平拆;只能靠业务侧打散或读副本。
- 问:为什么不能只靠加机器?答:热 key 的流量只会打到持有它的那个分片,加节点不会改变路由,瓶颈仍在单节点。
- 问:热点发现为什么要结合业务?答:监控只能看到"已经热了",业务能提前知道"将会热"(大促、热搜),提前预热与扩容才是治本。
- 问:本地缓存会不会把热点变成"脏数据放大器"?答:会,如果失效机制不可靠。给 L1 设 1~5 秒 TTL,或走广播失效,把不一致窗口控制在业务可接受范围。
小结
- 热点 Key 的本质是流量集中,解法核心是"把一份流量拆成多份"或"把请求挡在 Redis 之前"。
- 发现靠 LFU 采样 + 客户端埋点,治理靠本地缓存 → 副本 → 打散 → 限流。
- 热 key 与大 key 常常同时出现,治理时要一起看:既拆体积,也拆流量。