HashedWheelTimer-高效处理大量定时任务
背景
业务需要,要对app用户做个30s离线的功能,即:用户自登录开始,30s内无操作及下线。
常规思路1(单定时器轮询)
- 用户登录时用关联数组记录用户的uid & 用户最后操作时间,用户的任何操作也要更新对应的操作时间。
- 开一个定时任务,每秒触发一次,遍历该数组,(操作时间与30s求和)与当前时间比对判断是否该离线
优点
实现简单
缺点
效率较低,用户量一大玩脱,轮询的通病
常规思路2(多定时器)
- 和思路1一样,需要一个数组记录用户uid和最后操作时间
- 收到用户操作的请求后开一个定时器,30s后触发,任务内容是,检查该uid最后操作时间是否超过30s,是否该离线处理
优点
多定时器,效率高
缺点
用户的每次操作都开一个定时器,很耗资源,用户量大也是问题
HashedWheelTimer的实现思路
相信很多同学和我一样第一次看到这个名词,这个结构通俗的讲就是一个环形队列,专门设计用来处理大量定时任务的结构。
先看一张结构图
原理介绍
为了理解,我们假设业务:用户无操作时,8s即离线处理
如图,该环形队列有8个槽位,每个槽位有index值,定时任务设置为每1s顺时针走1格,聪明的你肯定想到了,我们可以把大量的定时任务通过一套算法落地到每一个槽位,这样当定时任务走到对应槽位时只需要获取该槽位所有的任务去处理即可
实现
- 8s超时,就创建一个index从1到8的环形队列(本质是个数组)
- 环上每一个槽位是一个数组(存储uid)
- 定义一个数组,记录uid落在环形队列的哪个槽位里
- 启动一个定时器,每隔1s,在上述环形队列中移动一格
- 定义一个index指针来标识刚检测过的槽的位置
用户请求到达时
- 从数组中,查找出这个uid存储在哪一个槽里
- 从这个槽的集合中,删除这个uid
- 将uid重新加入到新的槽集合中(具体是哪一个槽呢 ,Index指针所指向的上一个槽位,因为这个槽位,会被定时器在10s之后扫描到)
- 更新数组,记录这个uid对应槽的值
- 离线操作,定时器每秒种移动一个槽位,这个槽对应的集合中所有uid都应该被集体超时
优点
只需要1个定时器
定时器每1s只需要一次触发,消耗CPU很低
批量超时,当前扫到的槽位集合中所有元素都应该被离线