RocketMQ事务消息详解与实战
背景介绍
在分布式系统中,事务一致性是一个核心挑战。传统数据库事务难以直接应用于跨服务的场景,而分布式消息队列通过异步解耦提供了解决方案。然而,普通消息无法保证生产者的事务操作(如数据库更新)与消息发送的原子性,可能导致数据不一致。例如,订单系统在创建订单后发送消息,若事务失败但消息已发送,下游系统会处理错误数据。
RocketMQ 的事务消息机制解决了这一问题,确保本地事务与消息发送的原子性。它通过"两阶段提交"思想,支持生产者在执行本地事务后决定消息的最终状态(提交或回滚),从而保证一致性。事务消息广泛应用于分布式系统,如电商订单、支付系统等场景。
RocketMQ 对事务消息的支持
RocketMQ 的事务消息基于以下设计:
-
半消息(Half Message) :
- 生产者发送的消息首先作为"半消息"存储在 Broker 中,对消费者不可见。
- Broker 等待生产者确认消息状态(提交或回滚)。
-
事务状态检查:
- 如果生产者未及时确认,Broker 会定时回查生产者的事务状态,决定消息是提交(对消费者可见)还是回滚(丢弃)。
-
API 支持:
- RocketMQ 提供了
TransactionMQProducer
和TransactionListener
接口,用于发送事务消息并处理本地事务逻辑。
- RocketMQ 提供了
-
存储与可靠性:
- 事务消息存储在 Broker 的专用队列中,记录事务状态日志,确保高可用性和一致性。
相较于普通消息,事务消息增加了半消息阶段和状态检查机制,引入了额外的网络交互和回查逻辑,但保证了分布式一致性。
开发中需要调用的 API
在 RocketMQ 中,开发事务消息主要涉及以下 API:
-
TransactionMQProducer:
- 用于发送事务消息,需配置事务监听器。
- 示例:
TransactionMQProducer producer = new TransactionMQProducer("groupName");
-
TransactionListener:
-
定义本地事务执行和状态回查逻辑,实现以下两个方法:
executeLocalTransaction
:执行本地事务,返回事务状态(COMMIT, ROLLBACK, 或 UNKNOWN)。checkLocalTransaction
:Broker 回查时调用,返回事务最终状态。
-
示例:
typescriptpublic class TransactionListenerImpl implements TransactionListener { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { // 执行本地事务(如数据库操作) return LocalTransactionState.COMMIT_MESSAGE; // 或 ROLLBACK_MESSAGE, UNKNOWN } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { // 检查本地事务状态 return LocalTransactionState.COMMIT_MESSAGE; } }
-
-
发送事务消息:
- 使用
producer.sendMessageInTransaction(msg, arg)
发送事务消息,触发executeLocalTransaction
。
- 使用
-
配置与启动:
-
设置 Broker 地址、事务监听器,并启动生产者。
-
示例:
iniproducer.setNamesrvAddr("localhost:9876"); producer.setTransactionListener(new TransactionListenerImpl()); producer.start();
-
Broker 的处理流程与差异
Broker 在处理事务消息时,与普通消息相比有显著差异:
-
半消息存储:
- 收到事务消息后,Broker 存储为半消息,写入专门的事务消息队列(
RMQ_SYS_TRANS_HALF_TOPIC
),对消费者不可见。
- 收到事务消息后,Broker 存储为半消息,写入专门的事务消息队列(
-
事务状态记录:
- Broker 维护事务状态日志,记录半消息的元数据(如事务 ID)。
-
定时回查:
- Broker 定时(默认 60 秒)检查未确认的半消息,向生产者发送回查请求,调用
checkLocalTransaction
方法。 - 根据返回状态(COMMIT 或 ROLLBACK),Broker 将消息转为普通消息(投递给消费者)或丢弃。
- Broker 定时(默认 60 秒)检查未确认的半消息,向生产者发送回查请求,调用
-
性能开销:
- 事务消息增加了半消息存储、状态管理和回查的开销,吞吐量低于普通消息。
- 但通过异步提交和批量处理,RocketMQ 优化了性能。
-
与普通消息的差异:
- 普通消息直接存储并对消费者可见,无需状态确认。
- 事务消息引入半消息和回查机制,确保事务一致性,适用于高一致性场景。
- 事务消息需要额外的 Broker 配置(如启用
transactionCheckInterval
)。
实战项目:订单系统事务消息
以下是一个电商订单系统的实战案例,展示如何使用 RocketMQ 事务消息确保订单创建与消息发送的一致性。
项目背景
订单服务在创建订单后,需发送消息通知库存服务扣减库存。若订单创建失败,消息不应发送。我们使用 RocketMQ 事务消息保证一致性。
代码实现
java
package com.example.rocketmq;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.HashMap;
import java.util.Map;
public class OrderTransactionProducer {
private static final String NAMESRV_ADDR = "localhost:9876";
private static final String PRODUCER_GROUP = "order_producer_group";
private static final String TOPIC = "OrderTopic";
// 模拟数据库
private static Map<String, Order> db = new HashMap<>();
public static void main(String[] args) throws Exception {
// 初始化事务生产者
TransactionMQProducer producer = new TransactionMQProducer(PRODUCER_GROUP);
producer.setNamesrvAddr(NAMESRV_ADDR);
producer.setTransactionListener(new TransactionListenerImpl());
producer.start();
// 模拟订单数据
String orderId = "ORDER_001";
Message msg = new Message(TOPIC, ("OrderCreated:" + orderId).getBytes());
producer.sendMessageInTransaction(msg, orderId);
// 保持程序运行
Thread.sleep(60000);
producer.shutdown();
}
static class TransactionListenerImpl implements TransactionListener {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
String orderId = (String) arg;
try {
// 模拟创建订单
Order order = new Order(orderId, "PENDING");
db.put(orderId, order);
System.out.println("Order created: " + orderId);
// 假设事务成功
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
System.out.println("Order creation failed: " + orderId);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
String orderId = new String(msg.getBody()).split(":")[1];
// 检查订单状态
Order order = db.get(orderId);
if (order != null && "PENDING".equals(order.getStatus())) {
System.out.println("Check: Order exists, commit: " + orderId);
return LocalTransactionState.COMMIT_MESSAGE;
}
System.out.println("Check: Order not found, rollback: " + orderId);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
static class Order {
private String id;
private String status;
public Order(String id, String status) {
this.id = id;
this.status = status;
}
public String getStatus() {
return status;
}
}
}