RocketMQ下的事务消息

本文探讨了事务消息在分布式系统中的应用,特别是如何通过使用事务消息来减少远程过程调用导致的响应时间增加,提高用户体验及转账成功率。通过将一个大事务分割成多个小事务并利用事务消息的两阶段提交方案,解决了传统方法中事务处理效率低下的问题。

目的:首先要明确事务消息要解决的问题是什么

    错误理解:事务消息是业务级别的事务,A生产消息后,BCD都消费成功后才算事务完成,如果BCD其中任一未成功完成消费逻辑,即算事务执行失败,需要回滚。其实这个理解不能算是错误理解,理想情况下的分布式事务消息就应该是这样的。但是现实决定了要达到这一目的的成本是及其高昂的,为了满足这个要求而引入的bug很可能是比其解决的问题要多得多。并且,真实情况下,消费失败的情况是很少的。没有必要为了解决消费失败的问题而引入如此高的复杂度。

    正确理解:事务消息解决的问题是当采用分布式的服务方案时,如果不采用事务消息,那么为了解决其事务问题是会付出较高的RT成本。

    例如下面这种情况,Bob向Smith转账:

   

    从上图可以发现,总耗时增长了一倍。这还是网络情况良好的情况,如果其中哪个RPC环节出现了延迟,总时间就会更长。或者说如果需要执行的RPC调用更多的情况下,RT也会成倍增长。

    用户体验差不说,转账成功率也会降低。

    那么这个时候应该用什么思路去解决这个问题呢?上图集群环境下的转账事务本质上还是一个整体的大事务,我们将这个大事务分割成两个(或若干个)小事务,让他们几乎同时开始。具体如下图所示:

    (注:这里只是分成2个事务的话,效果还不是很明显,当分解的数量较多时,缩短RT的效果就很明显)

    

    但是这个时候就引入了一个新的问题,什么时候发送消息?我们有2个选择

    1.Bob事务单元执行前发送消息

    2.Bob事务单元执行完成后发送消息

    对于选择1,存在的问题是,如果消息发送了,Smith也消费了这条消息,但是Bob扣款失败了,怎么办?岂不是Smith凭空多出100块?选择2也拥有类似的问题,可能Smith凭空损失100块,Bob没有收到这个100块。

    那么这个时候怎么解决这个问题呢?有一个很简单的解决方法就是我们把发消息这个操作放在Bob的事务单元里面去不就好了么~ 这个方法的确可以解决问题,但是会引入另外一个比较严重的问题,发消息是一个网络操作,网络操作相对于本地操作来说时间都是相对较长的,把这么一个比较耗时的操作放在DB的事务中,这样合适么?会不会引起数据库性能下降呢?这样是不是就得不偿失了呢?

    这个时候,就需要我们的事务消息出场了,它既可以异步解耦,又可以保证消息的事务性,这里主要讲一下RocketMQ所实现的两步提交方案,即2PC方案。流程图阿里云RocketMQ官网就有,直接搬运如下:

    结合我们举的转账的例子就是这样:

    图二的①对应图一的①,图二的②对应图一的⑦,图二的③对应的就是最后的Commit: 投递消息。图二的④可以不用去管它。

    按照这样的架构去实现,就可以较好的解决之前提到的多集群下事务逻辑RT过长的问题。事务消息即保证了消息的事务性,又可以让其消费逻辑几乎同时开始处理,这样也就保证RT不会过高。

再多想一点:

    但是如果消费者消费失败了怎么办?按照理想情况,如果消费者消费失败,那么这个集群中所有的事务都需要回滚,不管是消息发送者还是其他的消息消费者,都需要回滚。但是这样带来的成本及潜在的bug就太大太多了。如果把一个消息中间件设计到这种程度,那么可以说是过度设计了。文章开头也说过,毕竟真实情况下,哪里会有这么多的消费失败呢?

参考文献:https://www.jianshu.com/p/453c6e7ff81c

RocketMQ 5.0 支持事务消息,这种消息类型允许将本地事务和发送消息的操作组合成一个分布式事务,确保两者的一致性。事务消息的使用和实现方式主要基于两阶段提交(2PC)机制和回查机制。 ### 事务消息的使用方式 在使用事务消息时,生产者首先发送一个**半消息**(Half Message),即消息已经被 Broker 接收并存储,但尚未对消费者可见。此时,生产者执行本地事务逻辑。根据本地事务的执行结果,生产者再决定是提交消息(Commit)还是回滚消息(Rollback)。 如果在本地事务执行过程中发生异常,或者 Broker 没有及时收到提交或回滚的指令,Broker 会通过**事务回查机制**主动向生产者查询事务状态,并根据回查结果决定消息是否对消费者可见。 ### 事务消息的实现方式 事务消息的实现主要包括以下几个步骤: 1. **发送半消息** 生产者调用 `sendMessageInTransaction` 方法发送半消息。此时,消息会被 Broker 存储,但不会立即投递给消费者。 2. **执行本地事务** 半消息发送成功后,生产者会执行本地事务逻辑。这通常涉及数据库操作或其他业务逻辑。 3. **提交或回滚事务** 本地事务执行完成后,生产者根据事务执行的结果向 Broker 提交(Commit)或回滚(Rollback)事务。如果提交,消息将变为可消费状态;如果回滚,消息将被删除。 4. **事务回查** 如果 Broker 在一定时间内未收到事务的提交或回滚指令,它会主动向生产者发起事务状态回查请求。生产者需要实现 `checkLocalTransaction` 方法来响应回查请求,并返回事务的最终状态。 ### 代码示例 以下是一个简单的事务消息使用示例: ```java import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; public class TransactionProducer { public static void main(String[] args) throws MQClientException, InterruptedException { TransactionMQProducer producer = new TransactionMQProducer("transaction_producer_group"); producer.setNamesrvAddr("localhost:9876"); producer.setTransactionListener(new TransactionListener() { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { // 执行本地事务逻辑 try { // 模拟本地事务操作 System.out.println("执行本地事务: " + new String(msg.getBody())); // 假设事务成功 return LocalTransactionState.COMMIT_MESSAGE; } catch (Exception e) { // 事务失败 return LocalTransactionState.ROLLBACK_MESSAGE; } } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { // 回查本地事务状态 System.out.println("回查事务状态: " + new String(msg.getBody())); // 假设事务已经提交 return LocalTransactionState.COMMIT_MESSAGE; } }); producer.start(); Message msg = new Message("TransactionTopic", "Hello Transaction".getBytes()); SendResult sendResult = producer.sendMessageInTransaction(msg, null); System.out.println("消息发送结果: " + sendResult); producer.shutdown(); } } ``` ### 事务消息的关键特性 - **两阶段提交(2PC)**:确保本地事务消息发送的原子性。 - **事务回查**:在事务状态不一致时,Broker 会主动回查生产者的事务状态,确保消息的最终一致性。 - **事务状态存储**:Broker 会记录事务消息的状态,并在事务提交或回滚后更新消息的可见性。 通过上述机制,RocketMQ 5.0 的事务消息能够有效地支持分布式事务场景,确保业务逻辑与消息发送的一致性[^1]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值