Skip to content

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 提供了 syncSendDelayMessageasyncSendDelayMessage

消费端必须查状态

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
  -> 库存、通知、优惠券服务各自处理

常见坑

  1. 收到延时消息直接关单。
    必须查询订单当前状态。

  2. 完全依赖延时消息,不做兜底任务。
    生产环境建议保留低频兜底扫描,处理极端漏消息或人工修复。

  3. 延时时间写死在代码里。
    产品经常改 15 分钟、30 分钟、2 小时,建议配置化。

练习题

  1. 为什么延时消息消费者必须查询订单状态?
  2. 延时消息能完全替代定时任务吗?
  3. 如果产品把关单时间从 30 分钟改成 15 分钟,代码应该怎么设计更稳?

参考答案

  1. 因为用户可能已经支付,延时消息只是触发检查。
  2. 不建议完全替代。生产中保留低频兜底扫描更稳。
  3. 把延时时间配置化,并记录每个订单创建时使用的过期时间。

来源

Built with VitePress. Deployed on Cloudflare Pages.