订单超时未支付自动关闭的几种实现方案

定时任务

实现思路比较简单。启动一个计划任务,每隔一定时间处理一次,这种处理方式只是适用比较小而简单的项目。
假设订单表的结构为:
t_order(oid, finish_time, stars, status, …)
然后,定时任务每隔一个 5 分钟(时间自己设定)等会这么做一次:
select oid from t_order where finish_time > 30分钟 and status=0;
update t_order set status=1 where oid in(超时订单id);
如果数据量很大,需要分页查询,分页 update,这将会是一个 for 循环。
但是,这种设计方案有一种明显的不足。
  1. 时效性差,会有一定的延迟,这个延迟时间最大就是每隔一定时间的大小,如果你设置每分钟定时轮询一次,那么理论上订单取消时间的最大误差就有一分钟,当然也可能更大,比如一分钟之内有大量数据,但是一分钟没处理完,那么下一分钟的就会顺延。
  2. 效率低。
  3. 对数据库的压力比较大。
但是,也有优势。
  1. 定时任务,实现起来简单。
  2. 也能很好的做分布式集群。

被动取消

这种实现方案和懒加载的思想一直,就是被动的取消订单。只有当用户或商户查询订单信息时,再判断该订单是否超时,如果超时再进行超时逻辑的处理。
但是这种方式依赖于用户的查询操作触发,这也就是说如果用户不进行查询订单的操作,该订单就永远不会被取消。不会取消的订单,也就可能意味着库存可能被占用。
所以,在实际实现上,可能是被动取消 + 定时任务的这种组合实现方式。这种情况下定时任务的时间可以设置的稍微“长“一点。
缺点:
  1. 会产生额外影响,比如统计,订单数,库存等产生影响。
  2. 影响用户体验,用户打开订单列表可能要处理大量数据,影响显示的实时性。
优点,同样是实现起来简单。

延时消息

这种方式是目前比较普遍的实现方式。
延时消息的这种实现方式,包含两个重要的数据结构:
  1. 环形队列,例如可以创建一个包含 2400 个 slot 的环形队列(本质是个数组)。
  2. 任务集合,环上每一个 slot 是一个 Set。
本质上,就是一个时间轮算法的一个实现。
时间轮算法可以类比于时钟,如上图箭头(指针)按某一个方向按固定频率轮动,每一次跳动称为一个 tick。这样可以看出定时轮由个3个重要的属性参数,ticksPerWheel(一轮的 tick 数),tickDuration(一个 tick 的持续时间)以及 timeUnit(时间单位),例如当 ticksPerWheel=60,tickDuration=1,timeUnit =秒,这就和现实中的始终的秒针走动完全类似了。
如果当前指针指在 1 上面,我有一个任务需要 4 秒以后执行,那么这个执行的线程回调或者消息将会被放在 5 上。那如果需要在20秒之后执行怎么办,由于这个环形结构槽数只到 8,如果要 20 秒,指针需要多转2圈。位置是在2圈之后的 5 上面(20 % 8 + 1)。
针对时间轮算法或者说延时消息,目前有很多消息队列都支持,比如 RocketMQ,RabbitMQ

扩展 JDK 的延时队列

JDK 自带了一个延时队列 DelayQueue,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入 DelayQueue 中的对象,是必须实现 Delayed 接口的。
如果公司允许,可以在此基础上,扩展成一个分布式的,支持集群的延时队列。但是缺点是,难度较高,小公司根本没有这个机会来做。

Redis 缓存

利用 redis 的 zset。zset是一个有序集合,每一个元素(member)都关联了一个 score,通过 score 排序来取集合中的值。
我们将订单超时时间戳与订单号分别设置为 score 和 member。系统扫描第一个元素判断是否超时,具体如下图所示。
但是,这种实现方式,在高并发条件下,多消费者可能会取到同一个订单号。当初,我的同事,不得已而又加来一个分布式锁来处理。但是,性能下降严重。后来又做了很多变种,最终还是采用了延时消息。
本站所有文章均由网友分享,仅用于参考学习用,请勿直接转载,如有侵权,请联系网站客服删除相关文章。若由于商用引起版权纠纷,一切责任均由使用者承担
极客文库 » 订单超时未支付自动关闭的几种实现方案

Leave a Reply

欢迎加入「极客文库」,成为原创作者从这里开始!

立即加入 了解更多