RocketMq事务消息

分布式事务

微服务倡导将复杂的系统拆分为若干个简单、职责单一、松耦合的服务,可以降低开发难度,便于敏捷开发。而对大多数中小型公司来说,实施微服务架构面临以下困难:

  • 单体应用拆分为分布式系统后,应用间的通讯和故障处理机制变得复杂
  • 微服务化后,一个简单的功能需要调用多个服务并操作多个数据库实现,数据一致性难以保障
  • 大量的微服务,导致其测试、维护、部署变得困难

为了保障微服务架构下数据的一致性,通常需要引入分布式事务来解决,当前比较流行的分布式解决方案如下。

基于二阶段提交的XA协议

  • 第一阶段:协调者询问所有参与者是否可以执行提交操作,参与者执行准备工作,例如为资源上锁,预留资源,写undo/redo log。
  • 第二阶段:若所有参与者回应“可提交”,则向所有参与者发送正式提交命令;若某个参与者回应“拒绝提交”,则向所有参与者发送回滚命令。

     

    image.png

XA协议保障了事务的强一致性,然而由于其采用的阻塞协议带来的巨大性能开销,难以达到较高的系统吞吐量。

TCC模式

TCC提供了一种全局事务解决方案,业务系统只需实现下面三个操作,即可完成分布式事务:

  • TRY:完成参与者业务检查并预留业务资源
  • CONFIRM:使用TRY阶段的预留业务资源,并执行业务
  • CANCEL:释放TRY结算预留的业务资源

     

    image.png

     

    TCC模式可以让业务更灵活地定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能,然而它对业务的侵入度较高,实现难度较大。

事务消息

通过消息的异步事务,可以保证本地事务和消息发送同时执行成功或失败,从而保证了数据的最终一致性。

  • 发送prepare消息,该消息对Consumer不可见
  • 执行本地事务
  • 若本地事务执行成功,则向MQ提交消息确认发送指令;若本地事务执行失败,则向MQ发送取消指令
  • 若MQ长时间未收到确认发送或取消发送的指令,则向业务系统询问本地事务状态,并做补偿处理

     

    image.png

RocketMq事务消息

image.png

客户端事务消息发送

image.png

 

发送prepare消息复用了普通消息发送,只是给消息增加了TRAN_MSG=true的属性,该属性决定prepare消息对Consumer不可见

消息写入CommitLog

image.png

 

事务消息的CommitLog写入和普通消息一致,它利用文件的顺序写来提升吞吐量,并采用文件映射的方式降低系统开销。

消息写入ConsumeQueue

image.png

 

ConsumeQueue的写入是采用异步方式完成的,ReputMessageSerivce作为一个长驻线程负责查询索引的构造和ConsumeQueue的写入,对于Prepare/Rollback消息不会写ConsumeQueue,从而保证它们对Consumer不可见

Broker端事务提交/回滚

image.png

 

Broker收到提交/回滚指令后,首先从根据offset从CommitLog读出原有的prepare消息,构造新的消息(修改事务状态标识)并写入Broker。对于一条事务消息,RocketMq会存储两条消息,存在一定资源浪费。其实它是为了保证随后的消费者能尽可能从PageCache中读到该消息,而不是读取较早的prepare消息(可能导致缺页中断),以提升系统吞吐量。

此外,rocketmq的最新版本(4.2.0)尚未支持本地事务的状态回查,这样可能存在由于网络抖动,导致commit/rollback未提交到broker导致prepare消息长期悬挂的风险。

在RocketMq的设计文档中,为事务消息增加了一张事务状态表,它维护了消息的Offset、事务状态(P/C/R)信息。可以采用如下思路实现事务消息的回查机制:

 

image.png

  • 在prepare消息写入commitLog后,可以通过CommitLogDispatcher写入一条事务状态记录(state=P)
  • 在提交/回滚事务时,根据transactionId找到对应的事务状态记录,并修改对应的事务状态
  • 通过长驻线程扫描事务状态表,对于超过一定时间的Prepare事务,发起对业务方的事务状态回查,根据回查结果修改事务状态,并向brokder发送相应的Commit/Rollback消息。
### RocketMQ 事务消息实现与用法 #### 什么是 RocketMQ 事务消息RocketMQ 提供了一种可靠的分布式事务解决方案,通过半消息机制来支持跨系统的分布式事务[^1]。这种方案的核心在于生产者发送的消息会被标记为“半消息”,只有当本地事务执行成功并显式确认后,该消息才会被消费者接收到。 --- #### 实现流程概述 1. **发送半消息**:生产者向 RocketMQ 发送一条半消息,在此阶段消息不会立即投递给消费者。 2. **执行本地事务逻辑**:生产者完成本地业务操作(如数据库更新),并向 RocketMQ 返回状态。 3. **提交或回滚**:如果本地事务成功,则通知 RocketMQ 提交消息;否则回滚消息。 4. **补偿机制**:如果 RocketMQ 长时间未收到生产者的提交/回滚请求,会主动回调生产者询问当前事务的状态。 --- #### 示例代码 以下是基于 Java 的 RocketMQ 事务消息的完整示例: ```java 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.LocalTransactionState; public class TransactionMessageExample { public static void main(String[] args) throws Exception { // 创建事务型 Producer 并指定组名 TransactionMQProducer producer = new TransactionMQProducer("transaction_group"); producer.setNamesrvAddr("localhost:9876"); // 设置 NameServer 地址 producer.start(); // 注册事务监听器 producer.setTransactionListener(new MyTransactionListener()); try { // 构造消息对象 Message msg = new Message("TopicTest", "TagA", ("Hello RocketMQ").getBytes()); // 发送事务消息 producer.sendMessageInTransaction(msg, null); } catch (Exception e) { System.out.println("Send transaction message failed."); } // 关闭 Producer producer.shutdown(); } } // 自定义事务监听器 class MyTransactionListener implements TransactionListener { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { // 执行本地事务逻辑 String orderId = new String(msg.getBody()); try { // 模拟本地事务处理 processOrder(orderId); // 处理订单 return LocalTransactionState.COMMIT_MESSAGE; // 如果成功则提交 } catch (Exception ex) { return LocalTransactionState.ROLLBACK_MESSAGE; // 如果失败则回滚 } } private void processOrder(String orderId) throws Exception { // 这里可以模拟实际的业务逻辑,比如修改数据库记录 if (orderId.equals("fail")) { throw new RuntimeException("Simulate a failure scenario."); // 故意抛出异常以触发回滚 } } @Override public LocalTransactionState checkLocalTransaction(Message msg) { // 补偿检查方法,用于处理超时情况下的事务状态查询 String orderId = new String(msg.getBody()); boolean isProcessedSuccessfully = checkOrderStatus(orderId); // 查询订单状态 return isProcessedSuccessfully ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE; } private boolean checkOrderStatus(String orderId) { // 检查订单是否已成功处理 return !orderId.equals("timeout"); // 假设 timeout 订单表示超时未处理 } } ``` 上述代码展示了如何创建一个事务消息生产者,并设置自定义的 `TransactionListener` 来处理本地事务逻辑以及补偿检查逻辑[^2]。 --- #### 测试场景分析 ##### 成功情况测试 在正常情况下,生产者发送半消息后能够顺利完成本地事务逻辑,并返回 `COMMIT_MESSAGE` 状态给 RocketMQ。此时,消费者将能接收并消费到这条消息。 ##### 异常场景测试 1. **本地事务失败**:如果本地事务未能成功完成(例如发生异常),则应返回 `ROLLBACK_MESSAGE`,从而阻止消息被最终提交。 2. **网络延迟或中断**:当 RocketMQ 收不到生产者的明确响应时,它会发起补偿检查调用 `checkLocalTransaction` 方法,以此决定是提交还是回滚消息[^3]。 --- #### 注意事项 - 生产环境中需确保 `executeLocalTransaction` 和 `checkLocalTransaction` 方法具有幂等性,防止重复执行带来副作用。 - 半消息的有效期默认为半小时,超过这个时间仍未得到确认将会自动丢弃。 - 资源管理器(RM)负责协调分支事务的提交或回滚过程,因此需要合理配置 RM 与事务协调器(TC)之间的通信链路。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值