官宣 8 点档:40 万 QPS 涌向一个 key
娱乐频道同事提前把「官宣」词条缓存预热好了,整点一到,流量准时砸下来:词条详情接口 topic:detail:9527 一个 key 扛了 40 万 QPS。奇的是 Redis 集群里其它节点都很闲,就这一个节点网卡被打满——这时才体会到 Cluster 的一个「盲区」:分片按槽切数据,而一个 key 永远只住在一个节点上,集群的分流能力对单个 key 无效。
加从库行不行?行,但算笔账:40 万 QPS 平摊到 20 个从库,每个 2 万——为了一个词条买 20 台机器,老板的表情会很难看。热 key 的解法不在「堆机器」,而在把流量摊开。先得找得到它。
先找到它:探测三招
热 key 最阴险的地方是它平时不存在,爆的时间点又最要命。三招探测:
- 客户端埋点:在访问 Redis 的入口组件上加滑动窗口计数,按 key 统计 QPS,超阈值告警。实时性最好,也是大厂的标配做法。
- redis-cli --hotkeys:官方自带,靠 LFU 计数找最热的 key——注意必须像第 9 篇说的那样配
allkeys-lfu系策略才有效,LRU 模式下没有访问频率数据。 - 代理层统计:如果业务走 proxy 访问 Redis,在 proxy 统一计数,多语言应用都能覆盖,代价是多一层组件。
# LFU 模式下,列出最热的 key(适合离线巡检)
redis-cli --hotkeys顺带厘清一对概念:大 key 是体积问题(一个 key 太重,第 3 篇讲过治理),热 key 是流量问题(一个 key 被访问太多次)。两者常结伴出现——热搜词条既大又热,那就是双重打击。
招式一:key 打散
既然一个 key 只能住一个节点,那就复制出 N 个分身:topic:detail:9527#1 到 #10,内容完全相同,但经过 CRC16 会落进 10 个不同的槽——40 万 QPS 被摊成每槽 4 万。写的时候 N 个分身全写,读的时候随机挑一个:
// 写:N 个分片全写,内容一致
// 读:随机挑一个分片,流量被摊平
String shard = "topic:detail:9527#" + ThreadLocalRandom.current().nextInt(1, 11);
String value = redisTemplate.opsForValue().get(shard);它的甜点是读依然强一致(分身内容相同),代价是写放大和业务代码要感知分片数。适合少写多读的场景——热搜词条恰好都是这种。
招式二:本地缓存,把流量挡在门外
更狠的思路是让这些请求根本别到 Redis:应用进程内放一层 Caffeine 本地缓存(第 6 篇多级缓存的 L1),热 key 进程内命中,Redis 压力归零。关键参数是 TTL 必须设短——3 到 5 秒,热点场景容忍几秒的脏读,换来 Redis 侧的彻底解放:
// 进程内扛热点:TTL 只有 3 秒,容忍短时脏读
Cache<String, String> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofSeconds(3))
.build();它治本,但也有代价:每个应用实例各存一份(内存换带宽);数据更新后各实例要在 TTL 窗口之后才看到新值,强一致需求别用这招。另外本地缓存过期那一瞬间,大量请求还是会涌向 Redis——别忘了第 5 篇的互斥锁或 singlefly 给回源上一把锁。
兜底:限流与降级
探测有延迟、打散要改造、本地缓存有脏读窗口——三招都来不及上的时候(比如热点毫无预兆地爆了),最后一道防线是第 6 篇讲过的限流降级保险丝:对该接口限流保住 Redis 和下游数据库,被限掉的请求返回默认数据或友好提示。难看,但活着。
方案怎么选
| 方案 | 生效速度 | 实时性代价 | 改造成本 |
|---|---|---|---|
| 扩从库 + 读写分离 | 中(要扩机器) | 无 | 低 |
| key 打散 | 快 | 无(读一致) | 中(读写都要改) |
| 本地缓存 | 快 | 秒级脏读 | 低 |
| 限流降级 | 立即 | 用户体验受损 | 中 |
实战的组合拳通常是:客户端埋点常开探测 + 可预测的热点(活动词条)提前预热进本地缓存和分片 + 突发热点靠本地缓存自动兜住 + 限流保命。四层防线,和第 4 篇防穿透的四道防线异曲同工——缓存这门课,学到最后都是「信任边界」的设计。
热 key 讲完,这个系列的主体内容就齐了。下一篇是收官复盘:把 15 篇的碎片串成一张作战地图——从一次接口优化开始,到穿透、击穿、雪崩、一致性、分布式锁,再到主从、哨兵、集群与性能调优,每个问题该翻回哪一篇、先查哪个方案。下篇见,给整个系列画个句号。
咖啡凉了,记得趁热喝。
评论 (0)