切换主题
08. 延时消息:30 分钟未支付自动关单
产品提出关单需求
产品说:
用户下单后 30 分钟不支付,订单要自动关闭,库存要释放。
第一反应可能是写定时任务扫库:
text
每分钟扫描 created_at < now - 30min 且状态为待支付的订单
-> 关闭订单
-> 释放库存这能跑,但订单量上来后会越来越重。大量扫描会给数据库压力,任务频率和准确性也不好平衡。
延时消息怎么解决
订单创建时,发送一条“30 分钟后检查订单是否支付”的延时消息。
text
创建订单
-> 保存订单
-> 发送延时消息 OrderPaymentTimeoutCheck,延时 30 分钟
30 分钟后消费者收到消息
-> 查询订单状态
-> 如果仍是待支付,则关闭订单
-> 如果已支付,则忽略注意:延时消息不是“30 分钟后必然关单”。它只是“30 分钟后触发一次检查”。
发送延时消息
java
public SendReceipt publishPaymentTimeoutCheck(OrderCreatedEvent event) {
return rocketMQClientTemplate.syncSendDelayMessage(
"order-timeout-topic",
event,
Duration.ofMinutes(30)
);
}官方 2.3.4 RocketMQClientTemplate 提供了 syncSendDelayMessage 和 asyncSendDelayMessage。
消费端必须查状态
java
public void handlePaymentTimeout(String orderId) {
Order order = orderRepository.getById(orderId);
if (!order.isWaitingPayment()) {
return;
}
order.closeForPaymentTimeout();
orderRepository.save(order);
stockService.release(orderId);
}不要收到延时消息就直接关单。用户可能已经支付,只是延时消息还没到。
小技巧
延时消息适合“到点检查”,不适合表达“未来一定执行”。业务状态永远以数据库当前状态为准。
关单事件可以继续发普通消息:
text
OrderPaymentTimeoutCheck
-> 关闭订单
-> 发送 OrderClosed
-> 库存、通知、优惠券服务各自处理常见坑
收到延时消息直接关单。
必须查询订单当前状态。完全依赖延时消息,不做兜底任务。
生产环境建议保留低频兜底扫描,处理极端漏消息或人工修复。延时时间写死在代码里。
产品经常改 15 分钟、30 分钟、2 小时,建议配置化。
练习题
- 为什么延时消息消费者必须查询订单状态?
- 延时消息能完全替代定时任务吗?
- 如果产品把关单时间从 30 分钟改成 15 分钟,代码应该怎么设计更稳?
参考答案
- 因为用户可能已经支付,延时消息只是触发检查。
- 不建议完全替代。生产中保留低频兜底扫描更稳。
- 把延时时间配置化,并记录每个订单创建时使用的过期时间。