当前位置: 首页 > news >正文

热点 Key 不是靠猜的:京东 HotKey 探测机制拆解

热点 Key 不是靠猜的:京东 HotKey 探测机制拆解

摘要:Redis 热 key 真正麻烦的地方,不是缓存本身,而是热点往往来得很突然,等慢日志、连接池、CPU 报警时已经晚了。本文结合京东云开发者社区的 JdHotkey 设计实践和京东零售开源仓库,拆解 HotKey 探测的 client、worker、dashboard、etcd 四层结构,讲清 Java 后端如何在缓存被打穿前发现热点 Key,并把它转成本地缓存、限流、降级或熔断动作。

1. 缓存被打穿之前,谁先发现热点?

昨天我们聊过热点互动洪峰:Bitmap 兜状态、Kafka 削峰、SDS 做计数快照、Caffeine + Redis + MySQL 做三级缓存、单飞锁合并 miss、限流退避防重建风暴。

但那篇文章默认了一个前提:

系统已经知道哪个 key 是热点。

真实线上系统里,这一步更难。热点可能来自秒杀商品、活动页、直播间、爬虫用户、恶意 IP,也可能来自某个接口突然被集中访问。

你事后看 Redis 慢日志、连接池、CPU、DB 回源量,当然能知道出事了;问题是,那已经晚了。

所以今天只讲一个问题:

热点 Key 到底是怎么被系统实时发现的?

京东开源过一个热 key 探测框架JdHotkey。京东云开发者社区的文章里说,它用于处理突发热点数据、热点用户、热点接口等场景,核心目标是在较短时间内识别热 key,并把它推送到服务端 JVM 内存里。Gitee 上的jd-platform-opensource/hotkey仓库也把它定位为“毫秒级探测热点数据,毫秒级推送至服务器集群内存”的框架。

它最值得学的不是“又加一层缓存”,而是把热点治理拆成了两步:

第一步:实时发现哪个 key 热了。 第二步:热 key 进 JVM,本地缓存、限流、降级或熔断。

2. 普通缓存为什么发现不了热点?

常见 cache-aside:

请求进来 -> 查 Redis -> 命中:返回 -> 未命中:查数据库 -> 回写 Redis

Redis 官方 cache-aside 文档也把它作为读多写少场景的常见解法:应用先查 Redis,miss 后回源主库,再把结果写回缓存。

问题是:普通 cache-aside 是被动的。

它只回答“这个请求是否命中缓存”,回答不了“这个 key 是否正在变成热点”。

比如:

/sku/query 整体 5 万 QPS 其中 skuId=10086 这一个 key 占 3 万 QPS

接口整体 QPS 看起来正常,Redis 集群平均负载也可能看起来还行。但这个 key 如果落在某个 Redis 分片上,那一个分片会先被打满。

热点 Key 的麻烦在于:

  • 平均 QPS 会掩盖单 key 峰值。
  • Redis 命中率高,不代表没有单分片热点。
  • 慢日志、CPU、连接池报警通常太晚。
  • 人工配置本地缓存跟不上突发流量。

所以热点治理的第一步不是缓存,而是探测。

3. 京东 HotKey 的一句话理解

一句话:

客户端采集访问 key,worker 聚合判断热度,dashboard 配规则,etcd 同步规则和热 key,客户端拿到热 key 后放进 JVM 内存,再由业务决定缓存、限流、降级或熔断。

它不是 Redis 插件,也不是改造 Redis 客户端。京东云文章里明确提到,JdHotkey 不依赖 Redis,本质是一个独立的热 key 探测系统。

它关心的是“key 字符串”:

类型key 示例
热商品skuId_10086
热店铺shopId_888
热接口/sku/query
刷子用户userId_123
恶意 IPip_1.2.3.4
用户访问某商品userId_123:/sku/query:skuId_10086

所以它不只服务缓存,还能服务限流、风控、降级和热点统计。

4. 四个组件:client、worker、dashboard、etcd

4.1 client:嵌进 Java 应用的探测入口

client 是业务应用里引入的 jar。

它负责:

  • 按规则收集待探测 key。
  • 定时批量上报给 worker。
  • 监听规则、worker 地址、热 key 变化。
  • 把探测出来的热 key 放入本地 Caffeine 缓存。
  • 对外提供“这个 key 是不是热 key”的判断能力。

业务代码应该把它当成热点信号源:

if(JdHotKeyStore.isHotKey(key)){// 本地缓存、限流、降级、默认值等业务逻辑}

注意:HotKey 框架只负责告诉你 key 是否热,不负责 value。比如 Redis 的sku:10086被判定为热 key 后,value 仍然需要业务自己加载,再放进本地缓存。

4.2 worker:真正做热度聚合和判断

worker 是独立部署的 Java 程序。

它负责:

  • 从 etcd 读取规则。
  • 接收 client 批量上报的 key。
  • 在时间窗口内聚合 key 出现次数。
  • 达到规则阈值后判定为 hot key。
  • 把 hot key 推送给对应 APP 的客户端。

worker 解决的是单机看不到全局热度的问题。

集群 100 台机器 某个 sku 总访问 5000 次/秒 平均到每台机器只有 50 次/秒

单机看起来不热,整体其实已经很热。worker 聚合后,才能在集群维度判断。

4.3 dashboard:规则和人工干预入口

dashboard 是控制台。

它负责:

  • 配置规则。
  • 查看热 key。
  • 手工添加或删除热 key。

规则示例:

skuId_ 开头:1 秒超过 100 次算热 userId_ 开头:2 秒超过 20 次算热 api_ 开头:1 秒超过 1000 次算热

商品、用户、接口、IP 的阈值不能混在一起。dashboard 的价值就是把业务规则从代码里拿出来。

4.4 etcd:配置中心和一致性通道

etcd 主要负责:

  • 存规则配置。
  • 存 worker 地址。
  • 存探测出来的 hot key。
  • 让 client、worker、dashboard 监听变化。

这里需要的是配置同步、监听、注册发现和一致性通知。etcd 更适合做这种协调型工作。

它保证:

一个 key 被判定为热 -> 所有相关 client 都能收到 一个 key 被人工删除 -> 所有相关 client 都能删除本地缓存

5. 探测链路:一个 key 怎么进 JVM 内存?

完整链路:

业务请求访问 key -> client 判断这个 key 是否命中规则 -> client 在本地窗口内累加 -> 每 500ms 批量发送待测 key 到 worker -> worker 按规则聚合计数 -> 达到阈值,判定为 hot key -> worker 推送 hot key 给 client -> client 放入本地 Caffeine -> 业务后续访问走本地缓存、限流或降级

几个关键点:

5.1 只有命中规则的 key 才上报

不是所有字符串都应该上报。

HotKey 探测不是日志系统,不负责记录所有访问。它只负责尽快识别可能有风险的 key。

5.2 client 先本地累加,再批量发送

京东文章里提到,client 默认每 500ms 批量发送一次待测 key;这段时间内,client 先在本地收集并累加。

这比每次访问都发给 worker 更合理:

  • 网络请求少。
  • 先做局部聚合。
  • worker 收到的是增量统计,不是每一次访问。

5.3 固定 key 发到固定 worker

同一个 key 不能分散到多个 worker,否则每个 worker 只看到局部计数。

所以可以用:

hash(key) % workerCount

固定 key 发送到固定 worker,聚合才准确。

5.4 热了之后不再反复上报

一个 key 已经被识别为热 key,后续重点就不是继续证明它热,而是让客户端在本地处理它。继续上报只会浪费探测资源。

6. HotKey 探测出来后,Java 侧怎么处理?

JdHotkey 只回答:

这个 key 是否热?

后续动作由业务自己决定。

6.1 本地 Caffeine 缓存

Redis 热 key 最常见的处理方式,是把 value 放进 JVM 本地缓存。

Stringkey="sku:"+skuId;if(JdHotKeyStore.isHotKey(key)){Productproduct=localCache.getIfPresent(key);if(product!=null){returnproduct;}product=redis.get(key);JdHotKeyStore.smartSet(key,product);returnproduct;}returnredis.get(key);

Caffeine 的配置文档支持maximumSizeexpireAfterWriterefreshAfterWrite等参数。所以本地缓存必须有容量、过期和刷新策略,不能当无界 Map。

6.2 限流或拒绝访问

如果 key 代表刷子用户或恶意 IP:

userId_123 ip_1.2.3.4

热了之后不一定缓存,而是限流、拒绝、返回默认响应。

6.3 接口降级

如果 key 代表某个接口:

api_/sku/query

热了之后可以短时间返回简化结果、默认值,或者关闭部分非核心计算。

6.4 单飞锁合并首次回源

HotKey 负责发现,single-flight 负责合并第一次加载。

CompletableFuture<Value>future=inflight.computeIfAbsent(key,k->CompletableFuture.supplyAsync(()->loadValue(k),executor).whenComplete((v,ex)->inflight.remove(k)));returnfuture.join();

否则热 key 第一次进本地缓存前,多个线程仍可能一起回源。

7. 它和普通二级缓存有什么区别?

很多人会问:这不就是 JVM 本地缓存吗?

不是。

二级缓存只是结果,HotKey 探测才是前置机制。

对比点普通二级缓存HotKey 探测
是否知道 key 热不热不知道,通常无脑缓存实时统计后判断
是否集群一致常常不一致通过 etcd 推送保持一致
是否能处理刷子用户不适合可以把用户 ID 当 key
是否能处理热接口不适合可以把接口路径当 key
是否需要业务规则较少规则是核心
主要价值减少部分 Redis/DB 访问在热点刚出现时快速保护系统

8. 面试怎么讲京东 HotKey?

如果面试官问:

Redis 热 key 怎么处理?

不要只说“加本地缓存”。

可以这样讲:

我会把热 key 问题拆成两步。 第一步是发现:不能靠人工猜,也不能只看接口平均 QPS。 可以参考京东 HotKey 的思路,在业务 client 侧采集待测 key,按规则批量上报 worker,由 worker 在时间窗口内聚合判断,达到阈值后通过 etcd 推送给所有客户端。 第二步是处理:客户端拿到 hot key 后,可以把 value 放进 Caffeine 本地缓存,也可以对用户、IP、接口做限流、降级或熔断。对于首次加载,还要配合单飞锁,避免多个线程同时回源。

这段回答比“Redis 热 key 放本地缓存”更完整,因为它讲清楚了发现、聚合、规则、通知和处理动作。

9. Java 后端落地清单

如果你不是直接引入 JdHotkey,而是自己做简化版,也要保留这些关键点:

  1. 先定义 key 维度:商品、用户、接口、IP、组合维度。
  2. 规则必须可配置:不同前缀、不同业务使用不同阈值。
  3. client 不全量上报:先按规则过滤,再本地聚合,再批量发送。
  4. 同一个 key 固定打到同一个 worker:否则聚合不准。
  5. worker 做窗口统计:目标是快速判断,不是精确日志。
  6. 热 key 要推送到全部相关 client:否则集群行为不一致。
  7. 本地缓存要有容量和过期:Caffeine 不能当无界 Map。
  8. 热 key 只是一种信号:后续动作可以是缓存、限流、降级、熔断、默认值。
  9. 首次加载要防并发回源:HotKey + 单飞锁一起用。
  10. 监控探测系统本身:worker QPS、队列长度、推送延迟、hot key 数量都要看。

总结

热点 Key 不是靠猜的。

真正可上线的高并发缓存系统,不能只在 Redis 被打穿之后再补救,而要在热点刚出现时就发现它。

京东 HotKey 的机制可以概括成:

client 采集 key worker 聚合判断 dashboard 配规则 etcd 同步推送 client 本地保护

这套机制背后的工程判断很清楚:

缓存治理不是从“缓存什么”开始,而是从“如何发现危险的 key”开始。

参考资料

  • 京东云开发者社区:京东毫秒级热 key 探测框架设计与实践,已完美支撑 618 大促:https://developer.jdcloud.com/article/2855
  • Gitee:京东零售 hotkey 开源仓库:https://gitee.com/jd-platform-opensource/hotkey
  • Redis 官方文档:Cache-aside:https://redis.io/docs/latest/develop/use-cases/cache-aside/
  • Caffeine Wiki:Specification:https://github.com/ben-manes/caffeine/wiki/Specification
http://www.cnnetsun.cn/news/2728679.html

相关文章:

  • 告别‘傻跑’:用ArduPilot速度PID和最大加速度参数,让你的无人船巡航更丝滑
  • DIY可缝制LED测试器:从原理到制作,解决电子制作中的LED测试难题
  • 告别会议纪要烦恼:用pyannote.audio 3.1.1自动分离并识别多人对话(附完整Python代码)
  • 从智能家居到智慧工厂:IoT、IIoT、AIoT的隐私保护实战,我用这7个方法避坑
  • 一站式浏览器Markdown渲染解决方案:彻底告别本地文档查看烦恼
  • 如何快速解除极域电子教室控制:面向学生的完整教学自主工具指南
  • STEM领域创意协作:从思维到实践,打造个人与团队创新力
  • VR办公打字体验研究:键盘视觉与手部反馈如何影响输入效率
  • LRCGET:彻底解决离线音乐库批量歌词同步的终极方案
  • 鸿蒙 HarmonyOS 6 | Pura X Max 鸿蒙原生适配 19:设置页在 Pura X Max 上改成分组布局
  • 【AI测试革命白皮书】:2024年全球头部科技公司已落地的7大智能测试整合范式
  • ArcMap布局视图实战:一张图搞定站点分布主图+全国位置副图(含比例尺指北针)
  • 3步掌握跨平台数据迁移:开源宝可梦存档编辑器完全指南
  • 利用个人设备构建分布式麦克风阵列实现高精度会议转录
  • 终极开源IPAM解决方案:NIPAP如何让IP地址管理变得简单高效
  • 告别高光干扰!用Python+OpenCV复现并行单像素成像,搞定复杂光照下的3D重建
  • DIY动圈式纸板扬声器:从电磁原理到动手制作的完整指南
  • QKeyMapper技术架构深度解析:跨设备输入映射与虚拟化方案实现
  • 从结绳记事到5G基站:用大唐杯仿真游戏串讲通信技术发展史(附避坑指南)
  • 界面自动化测试范式重构:Pywinauto Recorder在Windows生态中的战略定位与技术突破
  • 基于树莓派与热敏打印机的DIY拍立得相机:从硬件集成到软件控制全流程解析
  • C#工业通信开发包:EtherNet/IP协议栈源码,含IO适配器示例与PC测试工具
  • Office Tab Enterprise 12.00直装版:为Word/Excel/PPT/Outlook加标签,免注册适配2016与365
  • PyCharm玩家专属:用虚拟环境从源码跑通X-Anylabeling图像标注工具(含清华镜像加速)
  • DIY 12V 18Ah磷酸铁锂电池组:从电芯筛选到BMS安装全流程解析
  • 基于Makey Makey与Scratch的简易猫驱赶器制作指南
  • 用Espruino和JavaScript打造电动滑板遥控器:从硬件选型到固件开发全解析
  • RHEL8系统管理员必看:用yum和ELRepo源安全升级内核到最新稳定版(附kernel-ml与kernel-lt选择指南)
  • 运维效率翻倍:Xmanager + Xstart一键脚本,快速部署与管理多台Linux服务器桌面
  • 基于Arduino与火焰传感器的智能火灾报警系统设计与实现