jd-hotkey是防击穿的最后一道实时防线,能在毫秒级探测热key并下沉至本地缓存;当goods:10086秒级被调用50万次时,传统架构失能,唯有其独立worker聚合+etcd广播机制可实现无侵入、低延迟防御。

JD-hotkey 不是锦上添花的组件,而是防击穿的最后一道实时防线。当某个 goods:10086 在秒级内被调用 50 万次,而你的 Redis 分片单线程正排队处理第 499999 个请求时,传统缓存架构已经失能——此时只有毫秒级探测+本地缓存下沉,才能拦住后续流量。
热Key击穿的真实发生路径
Redis 单线程模型决定了它无法并行处理命令。一旦某个 Key 的请求频率远超该分片吞吐能力(比如 >2 万 QPS),后续请求就会在 client socket 缓冲区或 Redis 内部队列中堆积。这不是“慢”,而是“卡死”:
- 连接数耗尽:大量请求阻塞在同一个分片上,新连接被拒绝
- 超时雪崩:上游服务等待超时后重试/降级,进一步放大压力
- 缓存穿透:失效时所有请求穿透到 DB,DB 连接池瞬间打满
- 连带影响:同一分片上的其他 Key(如
user:777、shop:222)全部不可用
为什么 client 端埋点统计根本来不及
你在业务代码里加 hotCounter.inc(key),再异步上报聚合——这个链路本身就有延迟和丢失风险。更关键的是:统计滞后 = 防御失效:
- 上报延迟通常在 1–3 秒,而热 Key 峰值常在 200ms 内爆发
- 单节点统计不准:100 台机器每台只看到 500 QPS,但全局是 5 万 QPS
- 网络抖动或上报失败时,worker 根本收不到数据
- 你没法在
get执行前就知道它会不会成为热 Key
JD-hotkey 的 worker + etcd 协同机制怎么起效
它把“探测”从应用层彻底剥离,交由独立 worker 进程做全局聚合,靠 etcd 实现低延迟广播。整个通路不经过业务线程,无侵入、无阻塞:
-
client仅做轻量哈希上报(MD5(key).substring(0,8)),不传原始 key,内存占用极小 -
worker每 100ms 扫描一次所有 client 上报的哈希桶,滑动窗口计数(默认 5 秒窗口、100 次阈值) - 命中后立刻写入
/hotkey/{app}/list路径,所有client通过 etcd watch 实时感知,平均延迟 - 本地使用
Caffeine缓存,TTL 自动对齐业务逻辑(比如商品详情设为 300 秒)
部署时最容易被忽略的三个硬约束
很多人跑通 demo 就以为 ready,结果上线后探测失效或误报。核心问题不在代码,而在基础设施协同:
-
etcd必须用 3.4.x+,且--enable-v2=false会导致 client 初始化失败(JD-hotkey依赖 v2 API) -
worker和client的时间必须严格同步(误差 - 不能把
hotkey的report流量走公司统一网关——网关可能做负载均衡,导致同一 key 上报到不同 worker 实例,计数分散
etcd 的 watch 延迟、worker 的 GC 暂停、或是 client 上报时没做 key 归一化(比如 user:id=123 和 user:123 被当成两个 key)。这些细节不压测到百万级并发根本暴露不出来。











