梳理出一份清晰、可操作的RocketMQ事务消息实现指南。下面这个表格汇总了实现事务消息的四个核心组件及其职责,可以帮助你快速建立整体认知。
| 核心组件 | 主要职责 | 关键配置/方法 |
|---|---|---|
| 事务消息生产者 | 发送半消息,并触发本地事务执行 | TransactionMQProducer, sendMessageInTransaction |
| 事务监听器 | 核心:执行本地事务(如数据库操作)和响应Broker的状态回查 | TransactionListener接口,实现executeLocalTransaction和checkLocalTransaction方法 |
| 消息消费者 | 订阅并处理已提交的事务消息 | DefaultMQPushConsumer, 需实现幂等性逻辑 |
| Spring Boot集成 | 通过注解和模板简化配置 | @RocketMQTransactionListener, RocketMQTemplate |
🔧 核心实现步骤
1. 添加依赖
首先,在你的Maven项目中引入RocketMQ的Spring Boot Starter依赖。
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version> <!-- 请使用最新稳定版本 -->
</dependency>
2. 配置生产者与事务监听器(核心)
这是实现事务消息的关键。你需要配置一个事务生产者,并为其绑定一个事务监听器。
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;
@RocketMQTransactionListener(txProducerGroup = "tx-order-group") // 与此生产者组绑定
public class OrderTransactionListenerImpl implements RocketMQLocalTransactionListener {
/**
* 执行本地事务
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
// 1. 解析消息,获取业务数据
String orderId = (String) msg.getHeaders().get("orderId");
// 2. 执行本地业务逻辑,例如:创建订单、扣减库存
boolean isSuccess = orderService.createOrder(orderId);
// 3. 根据本地事务执行结果返回状态
if (isSuccess) {
// 本地事务成功,通知Broker提交消息
return RocketMQLocalTransactionState.COMMIT;
} else {
// 本地事务失败,通知Broker回滚消息
return RocketMQLocalTransactionState.ROLLBACK;
}
} catch (Exception e) {
// 记录日志并返回UNKNOWN,触发后续的事务状态回查
return RocketMQLocalTransactionState.UNKNOWN;
}
}
/**
* Broker事务状态回查(兜底机制)
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String orderId = (String) msg.getHeaders().get("orderId");
// 查询本地事务的最终状态(例如,检查订单在数据库中的最终状态)
OrderStatus status = orderService.queryOrderStatus(orderId);
switch (status) {
case PAID: // 业务已成功
return RocketMQLocalTransactionState.COMMIT;
case CANCELLED: // 业务已失败
return RocketMQLocalTransactionState.ROLLBACK;
default: // 状态仍不明确,继续等待下次回查
return RocketMQLocalTransactionState.UNKNOWN;
}
}
}
事务监听器的两个方法至关重要:
-
executeLocalTransaction:生产者发送半消息后,Broker会回调此方法 以执行本地事务(如订单入库)。此时消息对消费者不可见。 -
checkLocalTransaction:如果Broker长时间未收到生产者返回的最终状态(COMMIT/ROLLBACK),它会主动回查此方法,以确认本地事务的最终结果,防止因网络闪断导致数据不一致。
3. 发送事务消息
在Service层,使用 RocketMQTemplate发送事务消息。
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void createOrder(String orderId) {
// 1. 构建消息
Message<String> message = MessageBuilder.withPayload("订单支付消息")
.setHeader("orderId", orderId) // 设置业务ID,用于回查时定位
.build();
// 2. 发送事务消息
rocketMQTemplate.sendMessageInTransaction(
"tx-order-group", // 事务生产者组名,需与@RocketMQTransactionListener中定义的一致
"order-topic", // 目标Topic
message, // 消息体
orderId // 业务参数,会传递给executeLocalTransaction方法的arg参数
);
}
}
4. 消费消息(消费者端)
消费者需要实现幂等性,以应对可能出现的重复消息。
@Service
@RocketMQMessageListener(topic = "order-topic", consumerGroup = "order-consumer-group")
public class OrderConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
// 1. 解析消息
// 2. 【关键】幂等性校验:通过订单ID等唯一键判断消息是否已被处理过
if (orderLogService.isProcessed(message.getOrderId())) {
return; // 已处理则直接跳过
}
// 3. 处理业务(例如,通知发货)
orderService.notifyDelivery(message.getOrderId());
// 4. 记录处理状态,防止重复消费
orderLogService.markProcessed(message.getOrderId());
}
}
⚠️ 关键注意事项
-
幂等性至关重要 :由于网络重试或Broker的回查机制,消费者可能收到重复消息 。必须在消费端通过唯一业务ID(如订单号)进行幂等校验,确保业务逻辑即使被重复执行也不会产生错误结果。
-
避免耗时操作 :
executeLocalTransaction方法中不要进行耗时过长的操作,因为Broker有超时机制。如果长时间不返回结果,Broker会发起回查。 -
UNKNOWN状态的处理 :当本地事务执行结果不确定时,返回
UNKNOWN是合理的。Broker会定期回查,直到获得明确状态(COMMIT/ROLLBACK)。但应避免持续返回UNKNOWN,需设置合理的回查策略。 -
资源清理 :如果本地事务执行失败并返回
ROLLBACK,记得在本地进行相应的数据回滚或清理操作。
💡 事务消息流程回顾
简单回顾一下整个流程,帮助你加深理解:
-
发送半消息 :生产者发送消息,但此消息状态为
Prepared,对消费者不可见。 -
执行本地事务 :Broker回调生产者的
executeLocalTransaction方法执行本地业务。 -
提交/回滚 :生产者根据本地事务成功与否,向Broker发送
COMMIT(消息对消费者可见)或ROLLBACK(消息被删除)指令。 -
状态回查(兜底) :如果Broker未收到最终指令,会定期调用
checkLocalTransaction方法来确认最终状态。
希望这份详细的代码示例和解释能帮助你顺利实现RocketMQ的事务消息功能!如果你对特定环节有更多疑问,我们可以继续探讨。