切换主题
09. 事务消息:支付成功和消息发送要一致
线上问题
支付链路上线后,出现一个棘手问题:
支付状态已经落库成功,但履约服务没收到支付成功消息。
如果先发消息再改数据库,可能消息发出去了,本地事务失败。
如果先改数据库再发消息,可能数据库成功了,消息发送失败。
这就是本地事务和消息发送之间的一致性问题。
事务消息解决什么
事务消息的核心流程:
- 发送半消息。
- 执行业务本地事务。
- 本地事务成功则 commit 消息。
- 本地事务失败则 rollback 消息。
- 如果生产者中途异常,服务端会触发回查。
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 要能支持回查。至少要能从消息里拿到 orderId、paymentNo 这类业务主键。
事务回查不要写复杂逻辑,只查最终事实:
text
支付单状态 = PAID -> COMMIT
支付单状态 = FAILED/CLOSED -> ROLLBACK
支付单不存在或状态不明 -> 按业务策略处理常见坑
用事务消息解决消费失败。
消费失败是消费者侧问题,用重试、幂等、死信、补偿。回查查缓存。
缓存可能不一致。回查要查数据库或可靠状态源。本地事务里做太多外部调用。
本地事务越长,不确定窗口越大。
练习题
- 支付成功后发履约消息为什么适合事务消息?
- 事务回查应该查什么?
- 短信通知失败能靠事务消息解决吗?
参考答案
- 因为支付状态落库和支付成功消息发送需要保持一致。
- 查支付单或订单支付状态这类可靠事实。
- 不能。短信通知是消费者侧处理失败,应靠消费重试和补偿。
来源
- RocketMQ 事务消息:https://rocketmq.apache.org/docs/featureBehavior/04transactionmessage/
- 官方 transaction sample:https://github.com/apache/rocketmq-spring/blob/rocketmq-spring-all-2.3.4/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientProducerApplication.java
RocketMQTransactionChecker:https://github.com/apache/rocketmq-spring/blob/rocketmq-spring-all-2.3.4/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQTransactionChecker.java