RocketMQ事务消息概要
RocketMQ事务消息(Transactional Message)是指应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败。RocketMQ的事务消息提供类似 X/Open XA 的分布式事务功能,通过事务消息能达到分布式事务的最终一致。
Apache RocketMQ在4.3.0版中已经支持分布式事务消息,采用了2PC(两阶段提交)+ 补偿机制(事务状态回查)的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息,如下图所示。
RocketMQ 事务消息的工作流程
RocketMQ 的事务消息是指在消息发送方发送消息后,需要经过两阶段提交来确保消息的可靠性传递和处理。
undefined 发送消息:
○ 发送方将消息发送到 RocketMQ 服务端,但消息状态为“Prepared”。
undefined 执行本地事务:
○ RocketMQ 调用发送方应用程序注册的本地事务执行器(TransactionListener)来执行本地事务。
○ 本地事务执行成功后,应用程序根据事务执行结果返回 COMMIT 状态;如果执行失败,则返回 ROLLBACK 状态。
undefined 确认消息状态:
○ 根据本地事务执行结果,RocketMQ 服务端将消息状态更新为 COMMIT 或 ROLLBACK。
○ 如果发送方应用程序在规定时间内未提交事务执行结果,RocketMQ 服务端会执行回查机制。
undefined 回查机制:
○ RocketMQ 定期会检查处于“Prepared”状态的消息,向发送方应用程序发起回查请求。
○ 发送方应用程序接收到回查请求后,再次执行本地事务,并根据执行结果返回 COMMIT 或 ROLLBACK 状态。
通过以上流程,RocketMQ 的事务消息能够保证在消息发送和消费过程中的事务一致性和可靠性,确保消息在各个环节的正确处理。事务消息适用于需要确保消息生产和消费的原子性操作场景,例如订单支付、库存扣减等需要确保事务一致性的业务场景。
RocketMQ分布式事务原理
1 分布式事务应用场景
随着应用的拆分,从单体架构变成分布式架构,那么每个服务或者模块也会有自己的数据库。一个业务流程的完成需要经过多次的接口调用或者多条MQ消息的发送。
那基于上面的应用场景,应该如何设计发送消息的流程,才能让这两个操作要么都成功,要么都失败呢?其实,可以参照XA两阶段提交的思想,把发送消息分成两步,然后把操作本地数据库也包括在这个流程中。
那么,在介绍原理之前,先科普一下两个新的概念:
1、半消息(Half Message):也就是暂不能投递消费者的消息。发送方已经将消息成功发送到了 MQ
服务端,但是服务端未收到生产者对这条消息的二次确认,这个时候,这条消息会被标记为“暂不能投递”状态。
2、消息回查(Message Status Check):由于网络闪断、生产者应用重启等原因,导致某条事务消息的
二次确认丢失,MQ 服务端通过扫描发现某条消息长期处于“半消息”时,需要主动向消息生产者询问该消息的最终状态,要么是Commit,要么Rollback。
如图所示,一共分为七个步骤
第一步:生产者向 MQ 服务端发送消息。
第二步:MQ 服务端将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息。
第三步:发送方开始执行本地数据库事务逻辑。
第四步:发送方根据本地数据库事务执行结果向 MQ Server 提交二次确认,MQ Server 收到 Commit状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 Rollback 状态则删除半消息,订阅方将不会接受该消息。
第五步:在断网或者是应用重启的特殊情况下,按步骤4提交的二次确认最终未到达 MQ Server,经过固定时间后 MQ Server 将对该消息发起消息回查。
第六步:发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
第七步:发送方根据检查得到的本地事务的最终状态再次提交二次确认,MQ Server 仍按照步骤4对半消息进行操作(Commit/Rollback)。
RocketMQ事务消息使用限制
使用事务消息,有一些限制条件:
事务消息不支持延时消息和批量消息;
事务性消息可能不止一次被检查或消费,所以消费者端需要做好消费幂等;
为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次(即默认只会回查15次),我们可以通过 Broker 配置文件的 transactionCheckMax参数来修改此限制。如果已经检查某条消息超过 N 次的话( N = transactionCheckMax ), 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写 AbstractTransactionCheckListener 类来修改这个行为;
事务消息将在 Broker 配置文件中的参数 transactionMsgTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于 transactionMsgTimeout 参数;
提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。
事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。
1.事务消息执行时间限制:RocketMQ 要求事务消息的本地事务执行器(TransactionListener)在规定的时间内完成并返回事务执行结果,否则可能会触发回查机制。
2.回查机制的限制:如果发送方应用程序长时间未返回事务执行结果,RocketMQ 服务端会触发回查机制,这可能会增加系统的负担和网络开销。
3.不支持跨集群事务消息:RocketMQ 不支持跨集群的事务消息,即发送方和消费方需要处于同一个 RocketMQ 集群中。
4.事务消息的可靠性和性能权衡:由于事务消息需要经过两阶段提交,相比普通消息可能存在一定的性能损耗。
5.需要依赖本地事务执行器:发送方应用程序需要自行实现和注册本地事务执行器,确保本地事务的正确执行和结果反馈。
总的来说,RocketMQ 事务消息在确保消息可靠传递的同时,也需要开发者按照一定的规范来设计和实现本地事务执行器,以及处理可能的回查请求,这些都是在使用 RocketMQ 事务消息时需要考虑和遵循的限制。
RocketMQ事务消息怎么实现
在 RocketMQ 中,实现事务消息可以通过使用事务生产者(Transaction Producer)来完成。下面是一个简单的示例代码,演示了如何在 RocketMQ 中实现事务消息:
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TransactionProducer {
public static void main(String[] args) throws Exception {
TransactionMQProducer producer = new TransactionMQProducer("transaction_producer_group");
producer.setNamesrvAddr("your_namesrv_address");
// 定义事务监听器
TransactionListener transactionListener = new TransactionListenerImpl();
producer.setTransactionListener(transactionListener);
// 定义线程池来处理事务消息的预备、提交和回查
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2000), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("client-transaction-msg-check-thread");
return thread;
}
});
producer.setExecutorService(executor);
producer.start();
// 发送事务消息
Message message = new Message("YourTopic", "YourTag", "YourKeys", "YourMsg".getBytes());
producer.sendMessageInTransaction(message, null);
// 关闭生产者
producer.shutdown();
}
}
class TransactionListenerImpl implements TransactionListener {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 在此处执行本地事务,根据执行结果返回不同的状态
return LocalTransactionState.COMMIT_MESSAGE; // or ROLLBACK_MESSAGE or UNKNOW
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// 在此处检查本地事务的状态,并返回相应的状态
return LocalTransactionState.COMMIT_MESSAGE; // or ROLLBACK_MESSAGE or UNKNOW
}
}
以上代码中,我们创建了一个事务生产者 TransactionMQProducer,并设置了事务监听器 TransactionListener。在事务监听器的实现中,我们需要实现 executeLocalTransaction 方法来执行本地事务,以及 checkLocalTransaction 方法来检查本地事务的状态。在 executeLocalTransaction 中,根据本地事务的执行结果返回不同的状态,而在 checkLocalTransaction 中,根据本地事务的状态返回相应的状态。
使用事务消息时,需要确保消息发送的可靠性,以及本地事务的正确执行和状态的正确返回。在实际场景中,还需要根据业务逻辑来合理处理事务消息的执行和状态回查。