延迟消息与定时任务:订单超时取消应该怎么做

延迟消息适合业务触发后的未来动作,但订单超时取消这类场景还需要状态校验、幂等和补偿扫描。

延迟消息与定时任务:订单超时取消应该怎么做

“用户下单 30 分钟未支付自动取消”是延迟消息最经典的例子。

下单成功时发送一条 30 分钟后可见的消息。消费者收到后检查订单是否仍未支付,如果是,就取消订单。

Rendering diagram...

为什么不能只靠定时扫描

数据库定时扫描也能做订单超时取消,比如每分钟查 created_at < now - 30min and status = CREATED

这种方式简单可靠,但数据量大时扫描压力明显,也容易造成批量尖峰。

延迟消息可以把每个订单的超时动作拆成独立事件,减少集中扫描。

延迟消息不是最终判断

延迟消息到达时,订单可能已经支付、取消或关闭。所以消费者不能直接取消,必须先查状态。

UPDATE orders
SET status = 'CANCELLED'
WHERE id = 10001
  AND status = 'CREATED';

这个条件更新天然具备幂等性。只有仍处于 CREATED 的订单才会被取消。

几种实现方式

RocketMQ 原生支持定时/延时消息,适合业务消息场景。

RabbitMQ 可以通过 TTL + 死信交换机实现延迟队列,也可以使用延迟消息插件。

Redis ZSet 可以用时间戳作为 score,后台任务不断取到期任务。

数据库扫描最简单,但要控制批量和索引。

对比表

方案优点缺点
RocketMQ 延迟消息业务语义清晰依赖 MQ 能力
RabbitMQ TTL/DLX生态成熟设计细节较多
Redis ZSet灵活简单需要自己处理可靠性
数据库扫描最容易落地扫描压力和延迟
时间轮性能好实现和恢复复杂

补偿扫描仍然需要

延迟消息也可能丢失、消费失败或消费者长时间不可用。因此关键业务最好保留补偿扫描。

延迟消息负责及时触发,补偿任务负责兜底修正。

小结

延迟消息适合“某个事件发生后,未来某个时间再处理”的场景。订单超时取消的关键不是延迟本身,而是状态校验、幂等更新和补偿兜底。

参考链接