用 RocketMQ 事务消息改造支付超时流程,简单说就是:

  1. 下单时 “埋个定时提醒”用户下单后,系统往 MQ 里塞一条 “15 分钟后提醒我检查支付” 的消息(但这条消息先不发出去,算 “待确认” 状态)。同时本地创建订单,标记 “待支付”。如果订单创建成功,就确认这条消息 “15 分钟后发”;失败就直接删了消息。

  2. 支付成功就 “取消提醒”用户付钱了,系统就打个标记 “已支付”。等 15 分钟后那条提醒消息发过来时,一看订单已支付,就当没看见。

  3. 超时没付就 “执行取消”15 分钟到了,消息发过来,查订单还是 “待支付”,就自动取消订单(放库存、关支付)。

  4. 万一中间系统崩了MQ 会主动查订单状态,确定订单确实存在,就继续发提醒;订单没了,就扔了消息,不会乱操作。

好处:不用反复查数据库,时间到了才处理,准又省资源。

使用 RocketMQ 的事务消息改造支付超时判断流程,可实现可靠的超时通知最终一致性,避免传统定时任务轮询数据库的性能问题。以下是具体实现思路和流程:

核心场景

用户下单后,系统需在规定时间(如 15 分钟)内判断用户是否支付:

  • 若超时未支付,自动取消订单(释放库存、关闭支付通道等);
  • 若支付成功,取消超时判断,执行后续流程(发货、通知等)。

传统方案的痛点

  • 定时任务轮询数据库:高频次扫描未支付订单,浪费资源,且存在 “时间差”(如 15 分钟超时,可能 16 分钟才检测到);
  • 分布式锁控制并发:避免重复取消订单,但实现复杂,易引发死锁。

RocketMQ 事务消息改造方案

利用 RocketMQ 事务消息的 **“半消息 + 本地事务确认 + 回查机制”,结合延迟消息特性 **,实现精准的超时判断。

流程设计
  1. 下单时发送 “半事务消息”

    • 用户下单成功后,订单系统发送一条延迟消息(延迟时间 = 支付超时时间,如 15 分钟)到 RocketMQ,消息内容为订单 ID;
    • 此时消息为 “半消息”(暂不投递到消费者),需等待本地事务确认。
  2. 本地事务确认

    • 订单系统在发送半消息后,执行本地事务(如创建订单记录,状态为 “待支付”);
    • 若本地事务成功,向 RocketMQ 确认 “提交消息”(15 分钟后消息会被投递);
    • 若本地事务失败,向 RocketMQ 确认 “回滚消息”(消息直接丢弃,无需处理)。
  3. 支付成功时 “取消超时判断”

    • 用户支付成功后,支付系统调用订单系统接口,将订单状态更新为 “已支付”;
    • 订单系统向 RocketMQ 发送一条 **“取消超时通知” 的普通消息 **(或直接在本地记录 “已支付” 标记)。
  4. 超时消息消费(判断是否取消订单)

    • 15 分钟后,若消息未被取消,RocketMQ 将超时消息投递到 “订单超时消费者”;
    • 消费者接收到消息后,查询订单状态:
      • 若订单仍为 “待支付”:执行取消订单逻辑(释放库存、发送取消通知等);
      • 若订单已 “已支付”:忽略消息(不做处理)。
  5. 事务消息回查机制(兜底)

    • 若订单系统在确认 “提交 / 回滚” 半消息时宕机,RocketMQ 会定时回查订单系统的本地事务状态(通过订单 ID 查询订单是否存在);
    • 订单系统根据实际订单状态响应回查结果(存在则提交消息,不存在则回滚),确保消息状态最终一致。
关键技术点
  • 延迟消息:利用 RocketMQ 的延迟级别(如MESSAGE_DELAY_LEVEL=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h),设置 15 分钟对应的延迟级别(如15m需自定义配置)。
  • 幂等性处理:消费者需保证 “取消订单” 逻辑幂等(如通过订单 ID 加分布式锁,或在订单表加 “取消状态” 标记),避免重复取消。
  • 取消超时判断:支付成功后,可通过两种方式终止超时消息:
    1. 向 RocketMQ 发送 “取消通知”,消费者维护一个 “已支付订单 ID 集合”,收到超时消息时先检查集合;
    2. 直接在订单表中标记 “已支付”,消费者查询订单状态时自然忽略。
优势
  • 精准超时:基于 RocketMQ 延迟消息,超时判断时间误差极小(毫秒级);
  • 低资源消耗:无需定时轮询数据库,消息按需投递,性能更优;
  • 可靠性高:事务消息的回查机制确保极端情况下(如服务宕机)消息状态一致;
  • 解耦:订单系统、支付系统、库存系统通过消息异步通信,降低耦合。
代码示例(核心伪代码)
  1. 订单系统发送事务消息
// 1. 创建事务消息(延迟15分钟)
Message message = new Message("order_timeout_topic", orderId.getBytes());
message.setDelayTimeLevel(15); // 假设15对应15分钟

// 2. 发送半事务消息
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
    "order_producer_group", 
    message, 
    orderId // 本地事务参数
);

// 3. 本地事务执行(创建订单)
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
    String orderId = (String) arg;
    try {
        orderService.createOrder(orderId, "待支付"); // 本地事务:创建订单
        return LocalTransactionState.COMMIT_MESSAGE; // 确认提交消息
    } catch (Exception e) {
        return LocalTransactionState.ROLLBACK_MESSAGE; // 回滚消息
    }
}

// 4. 事务回查(判断订单是否存在)
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
    String orderId = new String(msg.getBody());
    Order order = orderService.getOrderById(orderId);
    if (order != null) {
        return LocalTransactionState.COMMIT_MESSAGE; // 订单存在,提交消息
    } else {
        return LocalTransactionState.ROLLBACK_MESSAGE; // 订单不存在,回滚
    }
}
  1. 支付成功后更新状态
// 支付系统回调接口
@PostMapping("/pay/success")
public void paySuccess(String orderId) {
    orderService.updateOrderStatus(orderId, "已支付"); // 更新订单状态
    // 可选:发送“取消超时”消息到消费者的本地缓存
    rocketMQTemplate.convertAndSend("order_cancel_timeout_topic", orderId);
}
  1. 超时消息消费者
@RocketMQMessageListener(topic = "order_timeout_topic", consumerGroup = "timeout_consumer_group")
public class OrderTimeoutConsumer implements RocketMQListener<String> {
    private final Set<String> paidOrderIds = new ConcurrentHashSet<>(); // 缓存已支付订单

    // 监听“取消超时”消息,更新缓存
    @RocketMQMessageListener(topic = "order_cancel_timeout_topic", consumerGroup = "cancel_consumer_group")
    public class CancelTimeoutConsumer implements RocketMQListener<String> {
        @Override
        public void onMessage(String orderId) {
            paidOrderIds.add(orderId);
        }
    }

    // 处理超时消息
    @Override
    public void onMessage(String orderId) {
        // 检查是否已支付
        if (paidOrderIds.contains(orderId) || orderService.getOrderStatus(orderId).equals("已支付")) {
            return; // 已支付,忽略
        }
        // 未支付,执行取消订单
        orderService.cancelOrder(orderId); // 幂等实现:先查状态再操作
    }
}

总结

通过 RocketMQ 事务消息改造后,支付超时判断流程实现了异步化、低耦合、高可靠,解决了传统定时任务的性能瓶颈,同时利用事务消息的特性确保极端场景下的数据一致性,适合高并发的电商、支付等场景。

更多推荐