• RocketMQ特性--事务消息是个啥,咋发出去的呢?


    1 概述

    分布式事务时指在多个系统或者多个数据库中的多个操作要么全部成功,要么全部失败,并且需要满足ACID四个特性:

    • Atomicity,原子性。操作是一个整体,要么全部成功,要么全部失败
    • Consistency,一致性。事务操作前后,数据必须是一致的。
    • Isolation,隔离性。多个事务同时执行时,不能互相干扰。
    • Durability,持久性。一旦事务被提交,数据改变就是永久性的。

    2 事务消息机制

    2.1 生产者发送事务消息

    事务消息的发送过程分为两个阶段,

    1. 发送事务消息
    2. 发送endTransaction消息

    2.2 TransactionMQProducer类

    发送过程的实现类是org.apache.rocketmq.client.producer.TransactionMQProducer,不仅能发送事务消息还能发送其他的消息。

    1. public class TransactionMQProducer extends DefaultMQProducer {
    2. private TransactionCheckListener transactionCheckListener;
    3. private int checkThreadPoolMinSize = 1;
    4. private int checkThreadPoolMaxSize = 1;
    5. private int checkRequestHoldMax = 2000;
    6. private ExecutorService executorService;
    7. private TransactionListener transactionListener;
    8. }

    2.2.3 transactionListener

    transactionListener事务监听器,主要功能是执行本地事务和执行事务回查

    1. public interface TransactionListener {
    2. LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);
    3. LocalTransactionState checkLocalTransaction(final MessageExt msg);
    4. }

    TransactionListener包含两个方法,

    • executeLocalTransaction()方法执行本地事务
    • checkLocalTransaction()方法是当生产者由于各种问题未发Commit或者Rollback消息给Broker时,Broker回调生产者查询本地事务状态的处理方法。

    2.2.4 executorService Broker回查请求处理的线程池。

    2.2.5 start()方法

    start()方法添加了一个initTransactionEnv(),作用是初始化事务消息的环境信息

    1. @Override
    2. public void start() throws MQClientException {
    3. this.defaultMQProducerImpl.initTransactionEnv();
    4. super.start();
    5. }

    从代码中我们可以看出,这里主要初始化了Broker回查请求处理的线程池,初始化的时候,可以指定初始化对象,当不指定的时候,将初始化一个单线程的线程池。

    1. public void initTransactionEnv() {
    2. TransactionMQProducer producer = (TransactionMQProducer) this.defaultMQProducer;
    3. if (producer.getExecutorService() != null) {
    4. this.checkExecutor = producer.getExecutorService();
    5. } else {
    6. this.checkRequestQueue = new LinkedBlockingQueue(producer.getCheckRequestHoldMax());
    7. this.checkExecutor = new ThreadPoolExecutor(
    8. producer.getCheckThreadPoolMinSize(),
    9. producer.getCheckThreadPoolMaxSize(),
    10. 1000 * 60,
    11. TimeUnit.MILLISECONDS,
    12. this.checkRequestQueue);
    13. }
    14. }

    2.2.6 shutdown()

    关闭生产者,回收生产者资源。

    1. @Override
    2. public void shutdown() {
    3. super.shutdown();
    4. this.defaultMQProducerImpl.destroyTransactionEnv();
    5. }

    destroyTransactionEnv()销毁了事务回查线程池,清除了回查任务队列

    3 发送事务消息的步骤

    3.1 发送Half消息

    事务消息的发送是通过sendMessageInTransaction方法来完成的。

    1. @Override
    2. public TransactionSendResult sendMessageInTransaction(final Message msg,
    3. final Object arg) throws MQClientException {
    4. if (null == this.transactionListener) {
    5. throw new MQClientException("TransactionListener is null", null);
    6. }
    7. msg.setTopic(NamespaceUtil.wrapNamespace(this.getNamespace(), msg.getTopic()));
    8. return this.defaultMQProducerImpl.sendMessageInTransaction(msg, null, arg);
    9. }

    3.1.1 数据校验

    判断transactionListener的值是否为null,topic和Namespace的空判断以及一些基础校验。还校验了message:

    1. Validators.checkMessage(msg, this.defaultMQProducer);

    3.1.2 消息预处理

    在方法sendMessageInTransaction()中,消息预处理主要是通过扩展字段设置消息类型。

    1. //表示当前消息是事务half消息
    2. MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
    3. //设置消息的group name和扩展字段。
    4. MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());

    3.1.3 发送事务消息

    这里直接调用了同步发送消息的发送方法,之前写过一节专门讲这个的。 juejin.cn/post/712652…

    1. try {
    2. sendResult = this.send(msg);
    3. } catch (Exception e) {
    4. throw new MQClientException("send message Exception", e);
    5. }

    3.1.4 执行本地事务

    消息发送成功之后,执行本地事务

    1. LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
    2. Throwable localException = null;
    3. switch (sendResult.getSendStatus()) {
    4. case SEND_OK: {
    5. try {
    6. if (sendResult.getTransactionId() != null) {
    7. msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
    8. }
    9. String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
    10. if (null != transactionId && !"".equals(transactionId)) {
    11. msg.setTransactionId(transactionId);
    12. }
    13. if (null != localTransactionExecuter) {
    14. localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg);
    15. } else if (transactionListener != null) {
    16. log.debug("Used new transaction API");
    17. localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
    18. }
    19. if (null == localTransactionState) {
    20. localTransactionState = LocalTransactionState.UNKNOW;
    21. }
    22. if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
    23. log.info("executeLocalTransactionBranch return {}", localTransactionState);
    24. log.info(msg.toString());
    25. }
    26. } catch (Throwable e) {
    27. log.info("executeLocalTransactionBranch exception", e);
    28. log.info(msg.toString());
    29. localException = e;
    30. }
    31. }
    32. break;
    33. case FLUSH_DISK_TIMEOUT:
    34. case FLUSH_SLAVE_TIMEOUT:
    35. case SLAVE_NOT_AVAILABLE:
    36. localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
    37. break;
    38. default:
    39. break;
    40. }

    3.2 发送Commit或者RollBack消息

    本地事务处理完成之后,会调用endTransaction()方法,通知Broker进行Commit或者Rollback.

    1. try {
    2. this.endTransaction(msg, sendResult, localTransactionState, localException);
    3. } catch (Exception e) {
    4. log.warn("local transaction execute " + localTransactionState + ", but end broker transaction failed", e);
    5. }
    1. public void endTransaction(
    2. final Message msg,
    3. //Half消息的发送结果
    4. final SendResult sendResult,
    5. //本地事务执行结果
    6. final LocalTransactionState localTransactionState,
    7. final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
    8. final MessageId id;
    9. if (sendResult.getOffsetMsgId() != null) {
    10. id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId());
    11. } else {
    12. id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
    13. }
    14. //事务消息的事务id
    15. String transactionId = sendResult.getTransactionId();
    16. final String destBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(defaultMQProducer.queueWithNamespace(sendResult.getMessageQueue()));
    17. //存储当前Half消息的Broker的服务器地址
    18. final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(destBrokerName);
    19. EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
    20. requestHeader.setTransactionId(transactionId);
    21. requestHeader.setCommitLogOffset(id.getOffset());
    22. switch (localTransactionState) {
    23. case COMMIT_MESSAGE:
    24. requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE);
    25. break;
    26. case ROLLBACK_MESSAGE:
    27. requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE);
    28. break;
    29. case UNKNOW:
    30. requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE);
    31. break;
    32. default:
    33. break;
    34. }
    35. doExecuteEndTransactionHook(msg, sendResult.getMsgId(), brokerAddr, localTransactionState, false);
    36. //生产者group
    37. requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
    38. //消息位移
    39. requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
    40. requestHeader.setMsgId(sendResult.getMsgId());
    41. String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
    42. //以发送oneway消息的方式通知Broker进行Commit或者Rollback
    43. this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
    44. this.defaultMQProducer.getSendMsgTimeout());
    45. }

    至此,事务消息发送并处理完毕。

     

  • 相关阅读:
    阿里云/腾讯云国际站代理:腾讯云国际站开户购买EdgeOne发布,安全加速一体化方案获业内认可
    高效的C++(一)
    三分钟看懂二极管的所有基础知识点
    【附源码】计算机毕业设计JAVA学习自律养成小程序前台.mp4
    《乔布斯传》英文原著重点词汇笔记(五)【 chapter three 】
    数字化转型导师坚鹏:数字化时代银行网点厅堂营销5大特点分析
    Runtime——methods成员变量,cache成员变量
    impala常用时间函数,date->string->timestamp互转
    web前端-JQuery
    ChatGPT简介及基本概念
  • 原文地址:https://blog.csdn.net/LBWNB_Java/article/details/126554310