定时任务的常见触发方式
大约 3 分钟
定时任务的常见触发方式
中间件项目中,经常会有下面的场景:
- client的定时重试
- client定时向server端发心跳包
- server端对client的判活
- ...
这种其实都是在某一个时间点触发一些任务,但是当任务量很大时,怎么做比较高效呢?
比如client定时向server发心跳包,在server端如何对client进行判活呢?一般我们的做法主要有下面的几种,当任务量很大的时候我们一般都会采样环形队列/HashedWheelTimer
方法。
轮询扫描
轮询扫描是最简单的处理方式,也非常的常见:
- 用一个Map来记录每一个client最近一次请求时间last_packet_time
- 当client有请求包来到,实时更新这个Map
- 同时有一个线程来专门的不断扫描这个map,比如当检查client的last_packet_time是否超过30s,如果超过则进行超时处理
多timer触发
- 用一个Map来记录每一个client最近一次请求时间last_packet_time
- 当某个client有请求包来到,实时更新这个Map,并同时对这个client的请求包启动一个timer,30s之后触发
- 每个client的请求包对应的timer触发后,看Map中,查看这个client的last_packet_time是否超过30s,如果超过则进行超时处理
环形队列/HashedWheelTimer
这种方案简单描述如下:
- 环形队列,本质是一个数组,比如30s超时,就创建一个index从0到30的环形队列
- 环上每一个slot是一个Set,表示:任务集合
- 同时还有一个Map,记录某个client落在环上的哪个slot里
- 同时启动一个timer,每隔1s,在上述环形队列中移动一格,移动到数组最后一个元素时候,又从第一个开始,也就是:0->1->2->3…->29->30->0…
- 有一个Current Index指针来标识刚检测过的slot
当某client有请求包到达时:
- 从Map结构中,查找出这个client存储在哪一个slot里
- 从这个slot的Set结构中,删除这个client
- 将client重新加入到新的slot中,具体是哪一个slot呢 => Current Index指针所指向的上一个slot,因为这个slot,会被timer在30s之后扫描到
- 更新Map,这个client对应slot的index值
哪些元素会被超时掉呢?
因为Current Index每秒种移动一个slot,这个slot对应的Set中所有client都应该被集体超时!如果最近30s有请求包来到,一定被放到Current Index的前一个slot了,Current Index所在的slot对应Set中所有元素,都是最近30s没有请求包来到的。所以,当没有超时时,Current Index扫到的每一个slot的Set中应该都没有元素。
优势:
- 只需要1个timer
- timer每1s只需要一次触发,消耗CPU很低
- 批量超时,Current Index扫到的slot,Set中所有元素都应该被超时掉
版权申明
本站点所有内容,版权均归https://wenchao.ren所有,除非明确授权,否则禁止一切形式的转载协议
打赏
