Skip to content

P06 订单超时自动取消是怎么实现的? ​

面试题:下单后 30 分钟未支付自动取消订单,有哪些实现方案?各自优缺点?

方案对比 ​

方案一:定时任务扫表(最常用) ​

text
定时任务(如每分钟一次)扫描"未支付且创建时间超过 30 分钟"的订单 → 批量取消
sql
UPDATE t_order SET status = 'CANCELLED'
WHERE status = 'UNPAID' AND create_time < NOW() - INTERVAL 30 MINUTE
  • 优点:实现简单、可靠,不会丢任务,适合大量订单批量处理;
  • 缺点:有时间误差(最多延迟一个扫描周期)、扫表量大时对 DB 有压力;
  • 优化:对 create_time + status 建索引、按时间分片扫描、记录上次扫描位置。

方案二:MQ 延迟消息(生产推荐) ​

  • RabbitMQ:死信队列 + TTL 或延迟插件;
  • RocketMQ:定时消息(延迟级别);
  • Kafka:无原生延迟,需自研或配合时间轮。

下单时发送一条 30 分钟后才投递的延迟消息,消费端收到后校验订单状态,未支付则取消。

  • 优点:实时性好、事件驱动;
  • 缺点:消息量大时积压,需保证消费幂等(收到消息时订单可能已支付/已取消)。

方案三:Redis 过期监听(不推荐) ​

给订单 key 设置过期时间 30 分钟,监听 keyexpired 事件触发取消。

  • 缺点:过期事件不保证及时可靠(key 到期后由惰性删除触发,事件可能延迟甚至丢失),只适合对实时性要求不高的场景。

方案四:Redisson DelayedQueue / 时间轮 ​

  • Redisson 基于 Redis ZSet 的延迟队列,到期才投递;
  • 时间轮(HashedWheelTimer):适合单机、延迟任务量可控的场景;
  • 缺点:进程重启会丢任务(需持久化补偿),分布式场景要选可靠实现。

关键设计点 ​

  1. 状态校验:取消前必须校验订单当前状态(只有 UNPAID 才能取消),防止把已支付订单取消;
  2. 幂等:取消操作支持重复执行(状态机 + 唯一约束);
  3. 补偿:定时扫表兜底 + 延迟消息双保险,消息丢了由扫表捞回来;
  4. 库存回补:取消后要释放锁定的库存(Redis 回加 + DB 回补)。

一句话总结 ​

生产环境常用"MQ 延迟消息做实时触发 + 定时扫表做兜底补偿",取消前校验状态,取消后回补库存,保证可靠与幂等。

基于 VitePress 重建