标签:静态 场景 segment 移动 自己 重复 需要 异步通知 队列
背景
在自己接触到的业务系统中,很多地方会有定时任务的需求,比如支付的交易超时自动关闭、连接超时、异步通知等等。常见的做法有:
1.考虑使用JDK中的Timer定时任务来实现
2.通过封装quartz搭建专门的调度平台来管理
目前项目中运用的是第2种。
看到netty中hashedwheeltimer原理,自己可以仿造一种数据结构,用来实现延时消息触发。
首先分析项目中哪些运用场景,通过延时的过程中数据的是否需要检测最终是否触发来划分静态的延时和动态的延时。
一、静态延时:不需要在延时的过程中判断是否触发定时任务,只是单纯地到指定时间触发任务即可,例如:交易成功通知业务系统。
二、动态延时:在延时的过程中并不是每个任务都需要执行,是有前提条件才能触发执行;例如:心跳检测,连接超时等。
场景一分析:支付成功异步通知
支付模块有一笔订单支付成功通知业务系统的定时任务,具体是支付流水交易如果支付成功了,那么由调度平台根据cron定义的时间来触发通知的任务。
假设支付流水表的结构为:t_jnl(jnl_no, pay_status,notify_status …),定时任务每一分钟执行一次:目前的场景可以简化为:
1.查询出支付成功的流水记录:select jnl_no from t_jnl where pay_status = 1 and notify_status =0;
2.调用业务系统接口,通知支付结果;
存在的问题:
①如果支付记录数很大,那么去查找满足条件的记录会造成数据库很大的压力。仅仅根据2个状态来查询的效率是很低的。
每次查询表数据,已经被执行过记录,仍然会被扫描(只是不会出现在结果集中),有重复计算的嫌疑。
②如果满足条件的支付流水足够多的话,至少每次不能一次性读取。需要分页查询,这将会是一个for循环。目前做法是定时任务触发时一次读取100条数据。
如果记录数超时定时任务中设定的数量(100),那么在后面的记录不会再本次中得到执行。
③假如一条记录恰好在刚执行任务后0.1s满足条件了(pay_status = 1),那么几乎要等待下一个周期被执行,时效性不好。误差时间有可能就是cron的设置时间t。
场景一改造:(静态延时)
为了解决上述场景存在的问题,引入下面的设计:右侧是通过一个数组进行封装的环形队列,类似一个时钟。根据cron来设置环形队列的segment,理解为一个独立的任务单元。左侧是每个任务单元的结构实现:set<Task>
以当前场景为例,cron设置时间t=60s,n=60,后台启动一个timer,这个timer每隔1s,在上述环形队列中移动一格,有一个Current Index指针来标识正在检测的segment。
那么改造的场景变为:
1.在支付成功后根据current Index所在位置和cron设置周期确认在环形队列上的segment下标和cyclenum后将数据插入环形队列中。
假设 current Index = 3,想要在60s后执行,数据插入第3+60=63个节点,但是环形队列最大长度为60,所以cyclenum=63/60=1,segment=3
2.task function是具体执行延时任务的方法
假设异步通知业务系统的方法为syncOrder(jnl_no) ,通知业务系统这笔流水支付成功了。
3.后台一致启动一个Timer,每隔t/n时间段,current index移动一个segment,当移动到当前的segment时候,渠道set<Task>中的cyclenum,
判断是否为0,如果cyclenum=0,立即执行task function,否则cyclenum -1。等待下个周期。
结论分析:
(1)无需与数据库进行交互,不用再轮询全部订单,效率高
(2)时效性好,精确到秒(设置timer的移动频率t和segment数量n可以控制精度)
(3)但是需要考虑数据量大的时候内存吃紧的情况。
标签:静态 场景 segment 移动 自己 重复 需要 异步通知 队列
原文地址:http://www.cnblogs.com/hupu-jr/p/7755674.html