分布式环境下对部分热数据(如redis热key,热请求)进行探测,并对探测结果及时同步到各个client实例的JVM内存的方案简述

可先阅读之前的这篇,有赞的热key探测及缓存方案。

常见场景

突发性的无法预先感知的热点数据请求,或者有阵发性明显热点数据的。

譬如突然大量请求都命中了redis的某个分片,造成该redis卡顿,影响其他请求。热key特性如 goodsId=100,突发1万请求该key。

譬如突然大量同一个用户的请求某一个或多个接口,呈现出攻击性访问的。热key特性如userId-99= /cart,/cartAdd,....。

目标

大幅降低热数据对下游服务(如redis、mysql)的冲击,在极短时间内探测出热点数据并缓存到jvm内存中。
小幅占用内存容量,不影响性能,随着热度过去后,释放占用的内存。

流程步骤

 ———————————————Client端—————————————-

### 接受到热key新增和删除事件,来自于worker


有一个interface,包含两个方法:
void newHotKey(HotKeyModel model);
void removeHotKey(HotKeyModel model);
HotKeyModel:对热key的包装类型
1 应包含createTime(时间戳)代表该key被探测出的时间
2 应包含key的名字,如 /queryGoods?goodsId=1
3 应包含appName,如userService
4 应包含该key的类型,如blackUserList,如hotRequest,如redisKey

直接实现该接口有诸多不便,应提供一个抽象类。对key的时间做一些校验,譬如已经明显过期的,就不要下发了。对于本地已存在的key,可以进行刷新过期时间的操作。对于不存在的key,进行新增操作。过期时间应该是可配置的,已热的key再次收到时,就刷新过期时间。
对于remove事件,应该在本地进行删除。删除事件很重要,有可能worker发了删除事件了,但是客户端没收到。所以需要双重发送,客户端也要监听etcd的key删除事件,再次确保已经删除了该key。
譬如可以封装一个cache接口,有add,remove,setExpire等方法。至于本地如何实现,是用caffeine还是guava cache,就不管了。

务必有etcd监听的原因,譬如5个worker,都能连上etcd,但是有一个就是连不上client,导致无法推送给他。他的key也发不到worker去。就得靠监听了。所以热key推送到client后,还要推到etcd一份,client再监听etcd的变化,决定是否也拉入自己的内存。那这就要求,每个推送事件有个唯一id


注意:
以上功能完成后,只是在本地保存了key,并没有保存value。所以还需要客户端来做一个aop切面,如果查询的对象在热key集合中,则获取到的value应该赋值给热key,尤其是redis的热key。
当然也有一些场景下,不需要value,譬如黑名单,热key直接就是userId-1001,只需要判断存在该热key即可,不需要value了。

### 和master交互的事件(待定,打算用ETCD)


客户端会配置所有的master地址,如master1、master2、master3并放入内存中。各个master间是无状态的,不设置master、slave之分,客户端根据自己的ip或者什么的hash一下,然后决定自己主要连哪个master,其他的master只维持个低频心跳即可。(master之间的数据强一致性,由master和worker之间维护)
连上后,可能的事件汇总:
1》连接某个master,维持心跳。首次连接,拉取所有worker信息到本地,保存为一个Array<WorkerInfo>。
2》连接被断开,或心跳超时无反应,断开与该master的连接,清除channel,并迅速切换下一个master,并拉取该master的全量信息(会不会卡住?)之后可以适当的搞个定时任务去拉取master缓存的信息。如果没有下一个master了,那就不管master了,做好重试即可,等master恢复了再次连接。不影响和worker的通信。
3》master重连事件,有两种情况,一种是当前在工作的master重连成功(譬如共1个master),另一种是非当前工作的master重连成功。List<MasterInfo>,masterInfo里有是否active的标志。
4》worker新增事件,由master推送过来WorkerAddEvent<array[workerInfo]>,然后建立起与新worker的连接,进行rehash。该新worker的index位置需要注意一下,Array[]比较好,当某个位置为空时,就可以填补上去。
5》worker掉线事件,由master推送过来并且自己也连不上该worker了就切掉该worker的channel(如果只是自己连不上了),并且Array<WorkerInfo>里将它的位置留空,置为null,将来要发给该worker的信息,都发到index+1那个worker去。

### 如果把master替换为ETCD

启动所有worker后,客户端连接etcd集群,etcd本身基于raft算法维持强一致性,从etcd拉取所有worker信息,并保存到本地内存中。
然后监听worker的新增和掉线事件,和上面差不多。
Worker在计算出热key后,会发往所有客户端,也会往etcd写一份,etcd里和client一样的数据。当有新client加入时,可以从etcd里获取初始化所有的热key。
Worker那里可以开个口子,允许别的想监听热key事件的端,也能监听到,譬如可以有个控制台来做保存,做大屏展示。提供一个filter链的形式,先通知所有的client,再通知这些额外的。

### 上报热key信息


提供三种模式:
1 tcp直连每次发送,适应于量不大时
2 tcp分批发送,每0.5秒或1秒发送一次
3 发送到MQ,让worker去消费。
提供一个顶级接口,IUpload
包含一个方法,send(Map<appName, List<KeyModel> list> map),appName就是应用名,list就是要探测的key的集合。
KeyModel即是对要探测的key的封装,包含了name,createTime,count等信息。

客户端需要根据配置文件,来决定是否要上传key信息,和上传哪些key信息,如只上传某些前缀开头的、不上传哪些前缀开头的。配置信息可以从etcd拉取,worker也会从etcd拉取配置信息。两端都要监听etcd配置信息变化。

为三种不同的模式,提供一个抽象类,并提供三个实现类。
提供一个filter过滤器,来过滤那些不该被上传的key信息。之后开始上传key信息。

### 统计功能


对JVM缓存命中率进行统计

### 应善用@Condition,提供默认的处理器
同时也给覆盖提供方法

# ——————————Worker端——————————


整体应采用观察者的模式,用eventBus进行解耦,各个内部事件监听器只管监听与自己相关的事件就好
1 接收到key事件,进行分发给不同线程
2 推送key事件, 先推appName下所有channel,再推额外的那些监听者,譬如etcd和其他的如界面控制台用来保存、监控、统计
3 监听etcd事件


### 接收客户端连接事件


接到客户端连接后,保存到一个map里,Map<appName, List<Channel>> map;将来推送时,也是根据appName推送的。
之后客户端维持了与worker的连接和心跳,如果心跳不到位,就干掉这个channel。


### 接收客户端发来的key事件


有2种模式
1 tcp发来的
2 消费MQ得来的
接到之后采用distruptor进行分发,多线程计算。
一旦计算完毕某个key的热情况,就可以删掉该key的信息了。
对于那些超时不热的,也要删掉。

### 监听etcd里客户端的配置变化信息


客户端会配置各种规则,rule,并且可能会动态改变。
改变后,worker也要相应地改变,譬如客户端修改了某个前缀不再参与计算。那worker也要相应地改变。虽然可能客户端已经不再发来被屏蔽的key了,但是自己也控制一下。

 ——————————控制台端——————————


主要功能:
1 提供控制台界面
2 查看所有在线的app,在线的worker
3 连接etcd,增删改查各客户端的配置信息,按app维度
4 监控热key情况(通过监听etcd,或从worker那开个口子)
5 查看客户端热key命中次数
6 日志查看

worker端:(如推送热key到客户端 — time)
client 端:如接收到热key----time
如worker新增、失连

控制台是数据落地的
 

原创文章 180 获赞 616 访问量 193万+

猜你喜欢

转载自blog.csdn.net/tianyaleixiaowu/article/details/103074533