0%

Hot Key解决方案

整理热Key问题及其解决方案,并介绍了Jd-hotkey热key探测方案

Hot Key(热 Key)

什么是热key

在高并发系统中某个Key短时间内产生了大量的访问,那么这个就变成了热Key,比如秒杀场景下瞬时产生的对产品的访问、某个帖子突然成为了热点等、或者恶意爬虫进行高频率地破坏性访问。

热Key危害

这种瞬时的高频词访问会给数据库带来巨大的负载,甚至导致系统瘫痪。一般来说,为了减轻mysql数据库的压力,会采用Redis等框架进行缓存,在高并发场景下Redis大多采用分片集群,Key会按照Hash规则映射到某个Redis分片上,日常运行是可以的,但如果某个数据成为了热Key,大量的访问请求会打到所在的Redis分片上去(Redis单机QPS大概在10w+),造成该分片集群瘫痪,由于Redis是单线程访问的,这将不仅影响了这个热key数据的访问,还会导致存储在该分片上的其他数据也无法访问。

如何解决热key问题

热Key复制

热Key复制存储到多个数据分片(Key_copy1Key_copy2…..)上来降低单个分片的负载,这种方式的问题在于需要提前得知哪些是热Key,提前进行处理,但很多时候是不太好预判的,如果对所有的Key都进行复制会带来很大的资源浪费问题。

1727496665529.png

读写分离

如果热Key主要来源于读请求,可以将集群设置为读写分离架构,主节点只负责处理写请求,提供一个代理服务将读请求分散到每个从节点上去。

1727496641668.png

二级缓存

读取到Redis中的信息后,借助CaffeineGuava等框架把信息缓存在本机中, 对于变更频率低、实时性要求高的数据放到本地缓存中可以大大提高访问速度,但是要考虑如何更新本地缓存来保证一致性问题,而且这种方案对于热Key场景来说,缓存的不都是热Key,会导致命中率低的问题。(所以关键问题是如何识别某个Key是否为热Key)

1727512158102.png

限流

限制单个用户的请求频率,识别异常用户拒绝其访问,如采用sentinel等,但其实从本质上来看并没有提高并发能力,而只是提前拦截了一些请求负载。

热Key识别

其实二级缓存是一个很好的解决方案,如果能精准识别热Key并缓存热Key,就能提高缓存命中率,提高访问速度。所以需要进行热key探测。这里我觉得可以给Redis集群做一个Proxy,进行负载均衡转发的同时进行一个Key访问频率的实时统计,如果是热Key就可以在用户请求这个Key的时候给他返回一个附带的信息,告知他这个是热Key,然后本地进行一个缓存处理。但是这样的话Proxy可能会产生新的性能瓶颈。后来了解到了京东零售团队开源的JD-Hot Key就可以实现高效的探测。下面将介绍以下这个热Key识别方案:

JD-Hot Key解决方案

京东hotkey框架(JD-hotkey)是京东app后台研发的一款高性能热数据探测中间件,用来实时探测出系统的热数据,并将热数据毫秒内推送至系统的业务集群服务器的JVM内存。该框架的优点如下:

  1. 轻量级对本地代码侵入小
  2. 实时性强,500ms即可探测出热key,而且可根据业务场景灵活调整
  3. 性能优秀,按照官方说明,一台8核8G的机器,在承担该框架热key探测计算任务时,每秒可以处理来自于数千台服务器发来的高达16万个的待测key,8核单机吞吐量在16万,16核机器每秒可达30万以上探测量。
  4. 低成本,高性能代表了低成本,仅采用10台机器,即可完成每秒近300万次的key探测任务

1727514958638.png

框架构成

该框架由四部分构成:

  • Client,在服务端通过引入Jar包的形式使用,可完成key上报,rule变化监听,worker信息变化更新,热key变化更新,本地缓存等功能。
  • etcd集群,作为配置中心,提高高效地监听订阅服务,主要用于存放规则配置,维护worker信息,存储探测出的热Key
  • worker集群,与etcd建立连接,为client提供热key统计功能,数量达到阈值后将热key推送到各个client
  • dashboard,可视化界面

运行流程

JD-HotKey的运行流程如下:

  1. client通过etcd获取规则、以及专属的worker地址信息,借助netty建立与worker的长连接
  2. client启动一个定时任务,没500ms(可配置),向worker发送一批待测key,基于hash规则进行映射,每个key会发送到同一个worker
  3. worker探测到热key后,推送给clientclient自动进行本地缓存

对于client来说该框架主要是提供了几个api供调用:

  • boolean JdHotKeyStore.isHotKey(String key)该方法会返回该key是否是热key,如果是返回true,如果不是返回false,并且会将key上报到探测集群进行数量计算。该方法通常用于判断只需要判断key是否热、不需要缓存value的场景,如刷子用户、接口访问频率等。

  • Object JdHotKeyStore.get(String key)该方法返回该key本地缓存的value值,可用于判断是热key后,再去获取本地缓存的value值,通常用于redis热key缓存

  • void JdHotKeyStore.smartSet(String key, Object value)方法给热key赋值value,如果是热key,该方法才会赋值,非热key,什么也不做

  • Object JdHotKeyStore.getValue(String key)该方法是一个整合方法,相当于isHotKey和get两个方法的整合,该方法直接返回本地缓存的value。 如果是热key,则存在两种情况,1是返回value,2是返回null。返回null是因为尚未给它set真正的value,返回非null说明已经调用过set方法了,本地缓存value有值了。 如果不是热key,则返回null,并且将key上报到探测集群进行数量探测。

可以看到使用还是很简单的,借助这几个api可以按照业务场景自定义热Key解决方案。

性能表现

参考官方技术文档:

  • etcd负载较轻,数千级别的客户端连接,平时秒级百来个的热key诞生,cpu占用率不超过5%,大部分时间在1%左右。
  • worker是压力最大的一环,对于8核8G的单机,平均160万次/10s的负载下,cpu占有率为70%,加上gc有时会造成更高的负载,差不多是单机的极限了。而对于16核16G的单机压测表明可以在30万qps情况下稳定工作半小时以上。

参考: