Hot Key(热 Key)
什么是热key
在高并发系统中某个Key
短时间内产生了大量的访问,那么这个就变成了热Key
,比如秒杀场景下瞬时产生的对产品的访问、某个帖子突然成为了热点等、或者恶意爬虫进行高频率地破坏性访问。
热Key危害
这种瞬时的高频词访问会给数据库带来巨大的负载,甚至导致系统瘫痪。一般来说,为了减轻mysql
数据库的压力,会采用Redis
等框架进行缓存,在高并发场景下Redis
大多采用分片集群,Key
会按照Hash
规则映射到某个Redis
分片上,日常运行是可以的,但如果某个数据成为了热Key
,大量的访问请求会打到所在的Redis
分片上去(Redis
单机QPS
大概在10w+
),造成该分片集群瘫痪,由于Redis
是单线程访问的,这将不仅影响了这个热key
数据的访问,还会导致存储在该分片上的其他数据也无法访问。
如何解决热key问题
热Key复制
将热Key
复制存储到多个数据分片(Key_copy1
,Key_copy2
…..)上来降低单个分片的负载,这种方式的问题在于需要提前得知哪些是热Key
,提前进行处理,但很多时候是不太好预判的,如果对所有的Key
都进行复制会带来很大的资源浪费问题。
读写分离
如果热Key
主要来源于读请求,可以将集群设置为读写分离架构,主节点只负责处理写请求,提供一个代理服务将读请求分散到每个从节点上去。
二级缓存
读取到Redis
中的信息后,借助Caffeine
、Guava
等框架把信息缓存在本机中, 对于变更频率低、实时性要求高的数据放到本地缓存中可以大大提高访问速度,但是要考虑如何更新本地缓存来保证一致性问题,而且这种方案对于热Key场景来说,缓存的不都是热Key,会导致命中率低的问题。(所以关键问题是如何识别某个Key是否为热Key)
限流
限制单个用户的请求频率,识别异常用户拒绝其访问,如采用sentinel等,但其实从本质上来看并没有提高并发能力,而只是提前拦截了一些请求负载。
热Key识别
其实二级缓存是一个很好的解决方案,如果能精准识别热Key
并缓存热Key
,就能提高缓存命中率,提高访问速度。所以需要进行热key探测。这里我觉得可以给Redis
集群做一个Proxy
,进行负载均衡转发的同时进行一个Key
访问频率的实时统计,如果是热Key
就可以在用户请求这个Key
的时候给他返回一个附带的信息,告知他这个是热Key
,然后本地进行一个缓存处理。但是这样的话Proxy
可能会产生新的性能瓶颈。后来了解到了京东零售团队开源的JD-Hot Key
就可以实现高效的探测。下面将介绍以下这个热Key识别方案:
JD-Hot Key解决方案
京东hotkey框架(JD-hotkey)是京东app后台研发的一款高性能热数据探测中间件,用来实时探测出系统的热数据,并将热数据毫秒内推送至系统的业务集群服务器的JVM内存。该框架的优点如下:
- 轻量级对本地代码侵入小
- 实时性强,
500ms
即可探测出热key,而且可根据业务场景灵活调整 - 性能优秀,按照官方说明,一台8核8G的机器,在承担该框架热key探测计算任务时,每秒可以处理来自于数千台服务器发来的高达16万个的待测key,8核单机吞吐量在16万,16核机器每秒可达30万以上探测量。
- 低成本,高性能代表了低成本,仅采用10台机器,即可完成每秒近300万次的key探测任务
框架构成
该框架由四部分构成:
Client
,在服务端通过引入Jar
包的形式使用,可完成key
上报,rule
变化监听,worker
信息变化更新,热key
变化更新,本地缓存等功能。etcd
集群,作为配置中心,提高高效地监听订阅服务,主要用于存放规则配置,维护worker
信息,存储探测出的热Key
等worker
集群,与etcd
建立连接,为client
提供热key
统计功能,数量达到阈值后将热key
推送到各个client
dashboard
,可视化界面
运行流程
JD-HotKey
的运行流程如下:
client
通过etcd
获取规则、以及专属的worker
地址信息,借助netty
建立与worker的长连接client
启动一个定时任务,没500ms
(可配置),向worker
发送一批待测key
,基于hash
规则进行映射,每个key
会发送到同一个worker
worker
探测到热key
后,推送给client
,client
自动进行本地缓存
对于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情况下稳定工作半小时以上。
参考: