统一来说,业务有“在一段时间之后,完成一个工作任务”的需求。
实现这种定时任务有哪些方法呢,来总结一下想到的方法。
一、定时轮询
这是一个比较直接的思路,启动一个计划任务,每隔一定时间处理一次,这种处理方式只是适用比较小而简单的项目。
假设订单表的结构为:t_order(oid, finish_time, stars, status, …),更具体的,定时任务每隔一个小时会这么做一次:
select oid from t_order where finish_time > 30minite and status=0;
update t_order set stars=5 and status=1 where oid in[…];
如果数据量很大,需要分页查询,分页update,这将会是一个for循环。

定时轮询的不足:
1、时效性差,会有一定的延迟,这个延迟时间最大就是每隔一定时间的大小,如果你设置每分钟定时轮询一次,那么理论上订单取消时间的最大误差就有一分钟,当然也可能更大,比如一分钟之内有大量数据,但是一分钟没处理完,那么下一分钟的就会顺延。
2、效率低。

二、被动取消
被动取消的方式很简单:只有当用户查询订单信息时,我们再判断该订单是否超时,如果超时再进行超时逻辑的处理。
但是这种方式依赖于用户的查询操作触发,这也就是说如果用户不进行查询订单的操作,该订单就永远不会被取消。
不足:
1、会产生额外影响
比如统计,订单数量等产生影响
2、影响用户体验
用户打开订单列表可能要处理大量数据,影响显示的实时性。

三、延时消息
延时消息设计与实现
高效延时消息,包含两个重要的数据结构:
(1)环形队列,例如可以创建一个包含3600个slot的环形队列(本质是个数组)
(2)任务集合,环上每一个slot是一个Set

同时,启动一个timer,这个timer每隔1s,在上述环形队列中移动一格,有一个Current Index指针来标识正在检测的slot。

Task结构中有两个很重要的属性:
(1)Cycle-Num:当Current Index第几圈扫描到这个Slot时,执行任务
(2)Task-Function:需要执行的任务指针

假设当前Current Index指向第一格,当有延时消息到达之后,例如希望3610秒之后,触发一个延时消息任务,只需:
(1)计算这个Task应该放在哪一个slot,现在指向1,3610秒之后,应该是第11格,所以这个Task应该放在第11个slot的Set中
(2)计算这个Task的Cycle-Num,由于环形队列是3600格(每秒移动一格,正好1小时),这个任务是3610秒后执行,所以应该绕3610/3600=1圈之后再执行,于是Cycle-Num=1

Current Index不停的移动,每秒移动到一个新slot,这个slot中对应的Set,每个Task看Cycle-Num是不是0:
(1)如果不是0,说明还需要多移动几圈,将Cycle-Num减1
(2)如果是0,说明马上要执行这个Task了,取出Task-Funciton执行(可以用单独的线程来执行Task),并把这个Task从Set中删除

使用了“延时消息”方案之后,“订单48小时后关闭评价”的需求,只需将在订单关闭时,触发一个48小时之后的延时消息即可:
(1)无需再轮询全部订单,效率高
(2)一个订单,任务只执行一次
(3)时效性好,精确到秒(控制timer移动频率可以控制精度)

四、总结
环形队列是一个实现“延时消息”的好方法,开源的MQ好像都不支持延迟消息,不妨自己实现一个简易的“延时消息队列”,能解决很多业务问题,并减少很多低效扫库的cron任务。

另外,关于MQ的可达性、幂等性未来撰文另述。

原文链接: http://www.architecy.com/archives/373 如果对于 超时 精度比较高, 超时 时间在24小时内,且不会有峰值压力的场景,推荐使用RocketMQ的定时消息解决 方案 。在电商业务下,许多 订单 超时 场景都在24小时以上,对于 超时 精度没有那么敏感,并且有海量 订单 需要批处理,推荐使用基于定时任务的跑批解决 方案 。 点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 10:33更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT 源码解析作业调度中间件 E... 一、轮询数据库 这种方式就是在保存 订单 的时候把 订单 超时 时间也一起保存进去,然后用定时任务去轮询数据库获取未支付的 订单 ,再去判断是否 超时 了。 但是!这种方法太捞了呀,而且也不具备实时性,比如我有个 订单 号为:123的 订单 是在10:00:00这个时间 超时 ,但是我的定时任务是每5分钟执行一次,它又恰好是在09:58:00执行了一次任务,那么它这个 大家好,我是宝哥!前言在开发中,往往会遇到一些关于延时任务的需求。比如最近大家都在忙抢回家的火车票,当你下了一个 订单 没有支付时,会有一个倒计时,提示你半小时之内支付,否则会 自动 取消。这样的场景是如何 实现 的呢?例如生成 订单 30 分钟未支付,则 自动 取消生成 订单 60 秒后,给用户发短信对上述的任务,我们给一个专业的名字来形容,那就是延时任务。那么这里就会产生一个问题,这个延时任务和定时任务的区别究竟... 我们介绍了11种 实现 订单 定时 关闭 方案 ,其中不同的 方案 各自都有优缺点,也各自适用于不同的场景中。 实现 的复杂度上(包含用到的框架的依赖及部署):Redission > RabbitMQ插件 > RabbitMQ死信队列 > RocketMQ延迟消息 ≈ Redis的zset > Redis过期监听 ≈ kafka时间轮 > 定时任务 > Netty的时间轮 > JDK自带的DelayQueue > 被动 关闭 。 日前拜读阿牛老师的大作 领导:谁再用定时任务 实现 关闭 订单 ,立马滚蛋! 发现其 方案 有若干瑕疵,特此抛砖引玉讨论一二。在电商、支付等领域,往往会有这样的场景,用户下单后放弃支付了,那这笔 订单 会在指定的时间段后进行 关闭 操作,细心的你一定发现了像某宝、某东都有这样的逻辑,而且时间很准确,误差在1s内;那他们是怎么 实现 的呢?一般 实现 的方法有几种:有一些 方案 虽然广为流传但存在着致命缺陷,不要用来 实现 延时任务在 Redis 官方手册的keyspace-notifications: timing-of-expired-ev 一、定时轮询 这是一个比较直接的思路,启动一个计划任务,每隔一定时间处理一次,这种处理方式只是适用比较小而简单的项目。 假设 订单 表的结构为:t_order(oid, finish_time, stars, status, …),更具体的,定时任务每隔一个小时会这么做一次: sele...