Skip to content

原文链接

1. 定时任务

定时任务去轮询数据库,取消即将超时的订单。

1.1 本地定时任务

适用于单机版的业务系统,实现方式非常多样:

  • 永动机线程:开启一个线程,通过sleep去完成定时,一些开源中间件的某些定时任务是通过这种方式实现的。
  • JDK Timer:JDK提供了Timer API,也提供了很多周期性的方法。
  • 延迟线程池:JDK还提供了延迟线程池ScheduledExecutorService,API和Timer类似。
  • Spring Task:Sprig框架也提供了一些定时任务的实现,使用起来更加简单。
  • Quartz:Quartz框架更进一步,提供了可以动态配置的线程池。

1.2 分布式定时任务

  • xxl-job:大众点评的许雪里开源的,一款基于MySQL的轻量级分布式定时任务框架。
  • elastic-job:当当开发的弹性分布式任务调度系统,功能很强大,相对重一些。

定时任务实现的优点是开发起来比较简单,但是它也有一些缺点:

  • 对数据库的压力很大,定时任务造成人为的波峰,执行的时刻数据库的压力会陡增
  • 计时不准,定时任务做不到非常精确的时间控制,比如半小时订单过期,但是定时任务很难卡准这个点

2. 被动取消

在文章开头的那个倒计时器,大家觉得是怎么做的呢?一般是客户端计时+服务端检查。
什么意思呢?就是这个倒计时由客户端去做,但是客户端定时去服务端检查,修正倒计时的时间。
那么,这个订单超时自动取消,也可以由

客户端去做:

  • 用户留在收银台的时候,客户端倒计时+主动查询订单状态,服务端每次都去检查一下订单是否超时、剩余时间
  • 用户每次进入订单相关的页面,查询订单的时候,服务端也检查一下订单是否超时

这种方式实现起来也比较简单,但是它也有

缺点

  • 依赖客户端,如果客户端不发起请求,订单可能永远没法过期,一直占用库存

当然,也可以被动取消 + 定时任务 ,通过定时任务去做兜底的操作。

3. 延时消息

第三种方案,就是利用延时消息了,可以使用RocketMQ、RabbitMQ、Kafka的延时消息,消息发送后,有一定延时才会投递。
使用延时消息的优点是比较高效、好扩展,缺点是引入了新的技术组件,增加了复杂度

4. Redis的zset实现

Step 1: 客户下单,生成订单号,并将订单号及超时时间添加到Redis的zset中

// 生成订单号
order_id = generate_order_id()
// 将订单号及超时时间添加到zset中
zadd redis_key timeout_timestamp order_id
  • generate_order_id(): 生成订单号的函数,可以根据具体业务逻辑进行实现。
  • redis_key: Redis中存储订单号及超时时间的key。
  • timeout_timestamp: 订单超时的时间戳。

Step 2: 定时检查zset中的订单是否超时,如果超时,则取消订单

// 获取当前时间戳
current_timestamp = get_current_timestamp()
// 查找超时订单
expired_orders = zrangebyscore redis_key 0 current_timestamp
// 取消订单
for order_id in expired_orders:
    cancel_order(order_id)
// 移除已取消订单
zremrangebyscore redis_key 0 current_timestamp

除了上面的四种,其实还有一些其它的方式,例如本地延迟队列、时间轮算法、Redis过期监听……

Released under the MIT License.