开发中我们经常会遇到一些需要定时来解决的业务场景。比如,有这样一个需求:“如果连续30s没有请求包(例如登录,消息,keepalive包),服务端就要将这个用户的状态置为离线”。
轮询处理
将所有任务都添加到某集合中,定时轮询扫描,如果达到条件则进行相关处理;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
方案的不足:
- 效率低下。已经被执行过记录,仍然会被扫描(只是不会出现在结果集中),存在大量的重复计算;
- 时效性差。时间误差取决于轮询的间隔;如果间隔过小,重复被扫描的次数更高,效率会变得更低下。
定时处理
每来一个任务,启动一个定时器,达到定时器时间,执行相关处理;
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
方案的不足:
- 定时数过多,导致内存使用率过高,容易导致崩溃。
环形队列处理
数据结构:
- 环形队列ListLoop,例如可以创建一个包含0-30的slot**环形队列**(本质是个数组);
- 每个环上的任务集合Slot,环上每一个slot是一个Set;
- 记录每个Task对应落到Slot的Map集合;
执行过程:
第一步:启动一个timer,每隔1s,在上述环形队列中移动一格,0->1->2->3…->29->30->0…
有一个CurrentSlotIndex指针来标识刚检测过的slot ;
第二步:当有某用户uid有请求包到达时,从Map结构中,查找出这个uid存储在哪一个slot里;
第三步:如果存在,从这个slot的Set结构中,删除这个uid,否则跳过该步骤;
第四步:将uid重新加入到新的slot中(CurrentSlotIndex指针所指向的上一个slot)因为这个slot,会被timer在30s之后扫描到
第五步:更新Map,重新设置该uid对应slot的index值
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
方案的优点:
- 无需再轮询全部订单,效率高
- 无重复执行,一个订单,任务只执行一次
- 效性好,精确到秒(控制timer移动频率可以控制精度)
参照文章:10w定时任务,如何高效触发超时、1分钟实现“延迟消息”功能
举一反三
上述展示描述了一种业务场景,通过环形队列的方式我们还可以处理很多类似场景。
- 某打车软件订单完成后,如果用户一直不评价,48小时后会将自动评价为5星;
- 某数据产品用户修改设置,1小时后生效;
- …