延迟消息与定时任务:订单超时取消应该怎么做
延迟消息适合业务触发后的未来动作,但订单超时取消这类场景还需要状态校验、幂等和补偿扫描。
延迟消息与定时任务:订单超时取消应该怎么做
“用户下单 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 | 灵活简单 | 需要自己处理可靠性 |
| 数据库扫描 | 最容易落地 | 扫描压力和延迟 |
| 时间轮 | 性能好 | 实现和恢复复杂 |
补偿扫描仍然需要
延迟消息也可能丢失、消费失败或消费者长时间不可用。因此关键业务最好保留补偿扫描。
延迟消息负责及时触发,补偿任务负责兜底修正。
小结
延迟消息适合“某个事件发生后,未来某个时间再处理”的场景。订单超时取消的关键不是延迟本身,而是状态校验、幂等更新和补偿兜底。