事务消息是 RocketMQ 提供的一种高级消息类型,用于解决分布式场景下,本地数据库事务与消息发送之间的一致性问题。它通过两阶段提交 和事务状态回查机制,确保本地事务执行与消息投递达到最终一致性,尤其适用于订单支付、积分变更等需要高可靠性的业务场景。
💡 事务消息的核心原理
事务消息的核心机制可以概括为以下两个阶段和一种补偿机制:
-
第一阶段:发送半消息(Half Message)
- 生产者向 Broker 发送一条半消息 。这条消息与普通消息不同,它已经持久化到服务端,但对消费者不可见,暂时不能被消费。
- Broker 收到半消息并持久化成功后,会向生产者返回确认响应。
-
第二阶段:提交或回滚
-
生产者开始执行本地事务(例如,操作本地数据库)。
-
根据本地事务的执行结果(成功或失败),生产者向 Broker 发送 二次确认指令(Commit 或 Rollback)。
- Commit:Broker 将半消息转换为正式消息,对消费者可见,等待被消费。
- Rollback:Broker 会回滚该事务,即删除半消息,消息不会被投递。
-
-
事务回查(Transaction Check)
- 这是关键的补偿机制 。如果因为网络闪断、生产者应用重启等原因,导致 Broker 长时间未收到二次确认,Broker 会主动向生产者发起消息回查。
- 生产者收到回查后,需要去检查该消息对应的本地事务的最终状态(例如查询数据库),并根据实际状态再次向 Broker 提交 Commit 或 Rollback 指令。这保证了即使在异常情况下,事务也能最终达成一致。
为了更直观地理解整个流程,下图概括了事务消息的完整生命周期:

🛠 在SpringBoot项目中实现事务消息
下面我们基于 rocketmq-spring-boot-starter来实现一个完整的事务消息示例,以"订单支付成功后通知积分服务增加积分"为场景。
1. 添加依赖
首先确保 pom.xml中包含必要的依赖:
xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
2. 配置生产者与事务监听器
核心是创建一个事务监听器,它包含了执行本地事务和处理事务回查的两个方法。
typescript
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.messaging.Message;
@Service
@RocketMQTransactionListener(txProducerGroup = "tx-order-group") // 与发送方组名一致
public class OrderTransactionListenerImpl implements RocketMQLocalTransactionListener {
@Autowired
private OrderService orderService;
/**
* 执行本地事务
* @param msg 收到的消息
* @param arg 调用sendMessageInTransaction时传入的额外参数
* @return 事务状态
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 从消息头或arg中获取业务ID,如订单ID
String orderId = (String) msg.getHeaders().get("orderId");
try {
// 执行本地业务逻辑,例如:更新订单状态为"支付成功"
boolean success = orderService.updateOrderStatus(orderId, OrderStatus.PAID);
// 根据执行结果返回提交或回滚
return success ? RocketMQLocalTransactionState.COMMIT : RocketMQLocalTransactionState.ROLLBACK;
} catch (Exception e) {
// 记录日志,返回UNKNOWN状态,等待Broker回查
return RocketMQLocalTransactionState.UNKNOWN;
}
}
/**
* 事务回查方法
* @param msg 收到的消息
* @return 事务状态
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String orderId = (String) msg.getHeaders().get("orderId");
// 根据orderId查询数据库,确认本地事务的最终状态
OrderStatus status = orderService.queryOrderStatus(orderId);
if (OrderStatus.PAID.equals(status)) {
// 本地事务已成功,提交消息
return RocketMQLocalTransactionState.COMMIT;
} else if (OrderStatus.FAILED.equals(status)) {
// 本地事务已失败,回滚消息
return RocketMQLocalTransactionState.ROLLBACK;
} else {
// 状态仍不明确,继续等待下次回查
return RocketMQLocalTransactionState.UNKNOWN;
}
}
}
3. 发送事务消息
在业务服务中,使用 RocketMQTemplate发送事务消息。
typescript
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void payOrder(String orderId) {
// 1. 构建消息
Message<String> message = MessageBuilder.withPayload("订单支付成功,增加积分")
.setHeader("orderId", orderId) // 设置业务ID,用于回查
.build();
// 2. 发送事务消息
// 参数1: 事务组名(需与监听器内txProducerGroup一致)
// 参数2: 主题(Topic)
// 参数3: 消息体
// 参数4: 可选参数,会传递给executeLocalTransaction方法的arg参数
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction("tx-order-group", "order-topic", message, orderId);
System.out.println("发送结果:" + result.getSendStatus());
}
}
4. 消费者端实现幂等性
事务消息只能保证消息生产端的一致性,消费端需要自行保证消息的幂等性,因为网络重试可能导致消息被重复消费。
typescript
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
@Service
@RocketMQMessageListener(topic = "order-topic", consumerGroup = "order-consumer-group")
public class OrderConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
// 1. 解析消息,获取订单ID
// 2. 【关键】幂等性校验:查询数据库或Redis,判断该订单的积分是否已经添加过
// if (已处理) { return; }
// 3. 执行业务逻辑(例如,为用户增加积分)
// creditService.addCredit(...);
// 4. 记录处理状态,标记该消息已处理
}
}
⚠️ 重要注意事项与最佳实践
- 事务状态的可查询性 :
checkLocalTransaction方法需要能够查询本地事务的最终状态。通常的做法是,在执行本地事务时,将事务状态(如订单状态)持久化到数据库中,以便回查时使用。 - 避免未知状态 :虽然
UNKNOWN状态是回查机制的保障,但在生产中应尽量明确返回COMMIT或ROLLBACK,避免大量消息进入回查流程,影响系统性能和增加复杂度。 - 消息回查配置:Broker 端有关于回查间隔和最大回查次数的配置,需要根据业务容忍度进行合理设置。
- 主题类型匹配 :事务消息必须发送到类型为
Transaction的主题上。