订单超时处理解决方案

1,709 阅读4分钟

方案一:定时任务轮询数据库

使用一个定时任务定时去扫描数据库,然后进行update/delete操作

优缺点

优点:简单易行,支持集群操作

缺点

  • 对服务器内存消耗大
  • 存在延迟,比如你每隔3分钟扫描一次,那最坏的延迟时间就是3分钟
  • 假设你的订单有几千万条,每隔几分钟这样扫描一次,数据库损耗极大

方案二:JDK延迟队列

使用JDK自带的DelayQueue来实现,这是一个无界阻塞队列。

优缺点

优点:效率高,任务触发时间延迟低。

缺点

  • 服务器重启后,数据全部消失,怕宕机
  • 集群扩展相当麻烦
  • 因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常
  • 代码复杂度较高

方案三:Netty的HashedWheelTimer时间轮算法

image.png

时间轮算法可以类比于时钟,如上图箭头(指针)按某一个方向按固定频率轮动,每一次跳动称为一个 tick。这样可以看出定时轮由个3个重要的属性参数,ticksPerWheel(一轮的tick数),tickDuration(一个tick的持续时间)以及 timeUnit(时间单位),例如当ticksPerWheel=60,tickDuration=1,timeUnit=秒,这就和现实中的始终的秒针走动完全类似了。

优缺点

优点:效率高,任务触发时间延迟时间比delayQueue低,代码复杂度比delayQueue低。

缺点:

  • 服务器重启后,数据全部消失,怕宕机
  • 集群扩展相当麻烦
  • 因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常

方案四:redis的zset集合/Keyspace Notifications(2.8以上)

zset

利用redis的zset,zset是一个有序集合,进行排序,然后不断消费

Keyspace Notifications

利用该机制可以在key失效之后,提供一个回调,实际上是redis会给客户端发送一个消息。(2.8版本以上)

优缺点

优点:

  • 由于使用Redis作为消息通道,消息都存储在Redis中。如果发送程序或者任务处理程序挂了,重启之后,还有重新处理数据的可能性。
  • 做集群扩展相当方便
  • 时间准确度高

缺点:

  • 需要额外进行redis维护
  • 并发环境下需要分布式锁

方案五:RabbitMq死信队列、Amazon SQS(延时队列)

Amazon SQS

如果您创建延迟队列,则发送到该队列的任何消息在延迟期间对用户都保持不可见。队列的默认(最小)延迟为 0 秒。最大延迟为 15 分钟。不建议通过特殊的方式延长延迟时间。

RabbitMq

采用rabbitMQ的延时队列。RabbitMQ具有以下两个特性,可以实现延迟队列

RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter

lRabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,用来控制队列内出现了deadletter,则按照这两个参数重新路由。

优缺点

优点:社区活跃,用的放心。横向扩展方便,同时消息支持持久化,有问题可回滚。

缺点:

  1. 额外增加一个死信交换机和一个死信队列的配置;
  2. RabbitMQ 是一个消息中间件,TTL 和 DLX 只是他的一个特性,将延迟队列绑定在一个功能软件的某一个特性上,可能会有风险。
  3. 消息队列具有先进先出的特点,如果第一个进入队列的消息 A 的延迟是10分钟,第二个进入队列的消息B 的延迟是5分钟,期望的是谁先到 TTL谁先出,但是事实是B已经到期了,而还要等到 A 的延迟10分钟结束A先出之后,B 才能出。所以在设计的时候需要考虑不同延迟的消息要放到不同的队列。

代码具体实现建议google,这里只是提出方案。

总结

在需要保证数据可靠性,建议采用redis或者mq方案

并发小、数据量不大,建议采用JDK或者时间轮算法