Skip to content

09. 事务消息:支付成功和消息发送要一致

线上问题

支付链路上线后,出现一个棘手问题:

支付状态已经落库成功,但履约服务没收到支付成功消息。

如果先发消息再改数据库,可能消息发出去了,本地事务失败。
如果先改数据库再发消息,可能数据库成功了,消息发送失败。

这就是本地事务和消息发送之间的一致性问题。

事务消息解决什么

事务消息的核心流程:

  1. 发送半消息。
  2. 执行业务本地事务。
  3. 本地事务成功则 commit 消息。
  4. 本地事务失败则 rollback 消息。
  5. 如果生产者中途异常,服务端会触发回查。
text
支付服务
  -> 发送半消息 OrderPaid
  -> 更新支付单状态
  -> commit 或 rollback

RocketMQ
  -> 不确定时回查支付服务
  -> 根据回查结果提交或回滚消息

发送事务消息

java
public void publishOrderPaidInTransaction(OrderPaidEvent event) throws ClientException {
    Message<OrderPaidEvent> message = MessageBuilder
            .withPayload(event)
            .setHeader("OrderId", event.orderId())
            .build();

    Pair<SendReceipt, Transaction> pair =
            rocketMQClientTemplate.sendMessageInTransaction("order-paid-topic", message);

    Transaction transaction = pair.getTransaction();

    if (payService.markPaid(event.orderId(), event.paymentNo())) {
        transaction.commit();
    } else {
        transaction.rollback();
    }
}

这段代码对应官方 sample 的写法:先拿到 Transaction,再根据本地事务结果 commit 或 rollback。

事务回查

java
@RocketMQTransactionListener
static class OrderPaidTransactionChecker implements RocketMQTransactionChecker {

    @Override
    public TransactionResolution check(MessageView messageView) {
        String orderId = messageView.getProperties().get("OrderId");
        if (payQueryService.isPaid(orderId)) {
            return TransactionResolution.COMMIT;
        }
        return TransactionResolution.ROLLBACK;
    }
}

回查逻辑必须查可靠存储,例如支付单表。不要靠内存状态判断。

什么时候用事务消息

场景是否适合
支付成功后发履约消息适合
订单创建后发短信通常不需要
本地事务和消息必须保持一致适合
消费端业务失败不靠事务消息解决,靠重试和补偿

事务消息解决的是生产者侧“本地事务和消息发送”的一致性,不解决消费者处理失败。

小技巧

事务消息的 payload 要能支持回查。至少要能从消息里拿到 orderIdpaymentNo 这类业务主键。

事务回查不要写复杂逻辑,只查最终事实:

text
支付单状态 = PAID -> COMMIT
支付单状态 = FAILED/CLOSED -> ROLLBACK
支付单不存在或状态不明 -> 按业务策略处理

常见坑

  1. 用事务消息解决消费失败。
    消费失败是消费者侧问题,用重试、幂等、死信、补偿。

  2. 回查查缓存。
    缓存可能不一致。回查要查数据库或可靠状态源。

  3. 本地事务里做太多外部调用。
    本地事务越长,不确定窗口越大。

练习题

  1. 支付成功后发履约消息为什么适合事务消息?
  2. 事务回查应该查什么?
  3. 短信通知失败能靠事务消息解决吗?

参考答案

  1. 因为支付状态落库和支付成功消息发送需要保持一致。
  2. 查支付单或订单支付状态这类可靠事实。
  3. 不能。短信通知是消费者侧处理失败,应靠消费重试和补偿。

来源

Built with VitePress. Deployed on Cloudflare Pages.