RocketMQ事务消息看这篇就够了!!!!

RocketMQ事务消息概述&样例

为什么要有事务消息?本地事务不能解决问题吗? 一个简单的例子: 写数据库+给MQ发送一条消息

scss 复制代码
transaction {
    updateDB()
    success = sendMessage()
    if success {
        commit()
    }
    rollback()
}

这个过程看起来没有任何问题,但是如果sendMessage后因为网络或者其他原因导致生产者没有收到sendResult,但是消息实际已经写入了MQ,这样回滚后的结果实际是写MQ成功,写DB失败,并不符合我们的预期,所以我们希望写DB和写MQ整体具有原子性,于是就有了事务消息

事务消息的使用

java 复制代码
public class TranProd {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        // 生成一个事务消息生产者
        TransactionMQProducer producer = new TransactionMQProducer("trance_produce_group");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.setTransactionListener(new TranListener());
        producer.start();

        Message message = new Message("tranTest", "hellow".getBytes());
        TransactionSendResult transactionSendResult = producer.sendMessageInTransaction(message, null);
        System.out.printf("%s\n", transactionSendResult);
        Thread.sleep(50000);
        producer.shutdown();

    }
}

// 事务Listener
public class TranListener implements TransactionListener {
    public TranListener() {
    }

    @Override
    public LocalTransactionState executeLocalTransaction(Message message, Object o) {
        // 需要执行的内容
        System.out.println("executeLocalTransaction");
        updateDB();
        return LocalTransactionState.UNKNOW;
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
        // 检查并提交事务消息
        System.out.println("checkLocalTransaction");
        checkDB();
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}

LocalTransactionState.UNKNOW: 未知的事务状态
LocalTransactionState.COMMIT_MESSAGE: 提交事务
LocalTransactionState.ROLLBACK_MESSAGE: 回滚事务

executeLocalTransaction: 用来执行主要的逻辑,将需要看作一个整体的操作放在里面,这里我return的事务状态是UNKNOW,其实这里就可以根据是否抛出异常来判断是不是commit/rollback事务 checkLocalTransaction: 用来检查事务的状态,可以在这个方法中利用select来检查DB是否写入成功了,在这个方法内也可以进行commit/rollback


一些约定:

代码段中...表示一些不重要的逻辑部分; 没有使用//注释的汉字是真实执行的代码逻辑

RocketMQ事务消息Client端---sendMessageInTransaction部分

首先我们从生产者来看事务的生产者代码

java 复制代码
public TransactionSendResult sendMessageInTransaction(final Message msg,
    final TransactionListener localTransactionListener, final Object arg)
    throws MQClientException {
    ...
    检查listener
    是否是延迟消息,如果是就清理延迟标记
    // 打上事务消息标记
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
    // 发送消息
    sendResult = this.send(msg);
    -------- 这里就要转到broker端了,下文的broker端代码对应的是生产者执行到这里
    
    switch (sendResult.getSendStatus()) {
        case SEND_OK: {
            ...
            检查事务ID
            // 执行本地事务
            // 这里就是执行上文中样例里的业务逻辑(写DB)
            transactionListener.executeLocalTransaction(msg, arg);   
        }
        break;
        case FLUSH_DISK_TIMEOUT:
        case FLUSH_SLAVE_TIMEOUT:
        case SLAVE_NOT_AVAILABLE:
            localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
            break;
        default:
            break;
    }
    // 通知broker端根据localTransactionState进行对应的操作,这个方法我们下面在看
    this.endTransaction(msg, sendResult, localTransactionState, localException);
    ...
    拼接result
}

从上面的这段代码我们可以看到发送事务消息生产者一共做了:

  • 给消息打上事务标记
  • 发送消息到broker
  • 如果消息发送成功就执行本地事务方法executeLocalTransaction,设置LocalTransactionState
  • 执行endTransaction方法(下文中具体看这个方法的用途)

Broker端---处理sendMessage

这里我们看下生产者发送了事务消息到broker端,broker如何处理事务消息

java 复制代码
public RemotingCommand processRequest(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
     ...
     response = this.sendMessage(ctx, request, sendMessageContext, requestHeader, mappingContext,
                        (ctx12, response12) -> executeSendMessageHookAfter(response12, ctx12));
 }
 
 
 // sendMessage方法
 public RemotingCommand sendMessage(final ChannelHandlerContext ctx,
        final RemotingCommand request,
        final SendMessageContext sendMessageContext,
        final SendMessageRequestHeader requestHeader,
        final TopicQueueMappingContext mappingContext,
        final SendMessageCallback sendMessageCallback) throws RemotingCommandException {
    ...
    // 根据PROPERTY_TRANSACTION_PREPARED这个标记来判断是不是事务消息
    String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED);
    ...
    putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);
    ...       
}   

// 跟着prepareMessage方法继续往下走最终会走到parseHalfMessageInner方法
private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) {
        ...
        设置PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX
        
        // 把真实的topic信息换存在消息中
        MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, msgInner.getTopic());
        MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID,
            String.valueOf(msgInner.getQueueId()));
        
        ...
        set了一个flag TRANSACTION_NOT_TYPE
        
        // 这里将topic设置成了RMQ_SYS_TRANS_HALF_TOPIC
        // 也就是说所有的事务消息最终都先写入到了RMQ_SYS_TRANS_HALF_TOPIC这个topic中
        msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
        msgInner.setQueueId(0);
        msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
        return msgInner;
    }

通过上面的broker端代码我们可以知道,broker在处理sendMessage的时候做了:

  • 将真实的topic信息缓存在了消息中
  • 将消息写入了RMQ_SYS_TRANS_HALF_TOPIC这个topic中,并且落盘

RMQ_SYS_TRANS_HALF_TOPIC是一个单队列topic,所有的事务消息都会先写入这个topic然后根据事务的状态来进行commit/rollback

Client端----endTransaction方法

这个时候生产者已经收到了sendResult,执行了listener中的executeLocalTransaction方法后得到了一个LocalTransactionState,这个时候调用endTransaction方法看看是做什么

java 复制代码
public void endTransaction(...) {
    switch (localTransactionState) {
        case COMMIT_MESSAGE:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE);
            break;
        case ROLLBACK_MESSAGE:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE);
            break;
        case UNKNOW:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE);
            break;
        default:
            break;
    }
    
   //  RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, requestHeader);
   //  上面这行代码就是真实发往broker的request头,根据END_TRANSACTION就可以在broker端找到broker的处理逻辑
     DefaultMQProducerImpl.this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, thisHeader, remark,
                        3000);
}

实际上从生产者端的代码能得到的内容不多还得看broker是如何处理的

Broker处理事务状态

java 复制代码
// 注册事件处理方法
this.remotingServer.registerProcessor(RequestCode.END_TRANSACTION, endTransactionProcessor, this.endTransactionExecutor);

public class EndTransactionProcessor implements NettyRequestProcessor {
    ...
    // 实际上broker只处理回滚或者commit,不处理UNKNOW
    if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
        // commitMessage代码在下面代码1中,实际上只是获取了要commit的消息
        result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
        ...
        // endMessageTransaction代码在下面的代码2中,实际上是拿到了真是的topic信息
        MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
        // 清理掉事务消息的标记
        MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED);
        RemotingCommand sendResult = sendFinalMessage(msgInner);
        
        if (sendResult.getCode() == ResponseCode.SUCCESS) {
        // deletePrepareMessage代码分析在下面的代码3中,
            this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
        }
        
    } else if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {
    // rollback和commit之间的区别就是是否写入消息到真实topic
        this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
    }
}

这里记录代码1:commitMessage

java 复制代码
// 代码1:commitMessage
// 这里其实没有真实的commitMessage,实际上只是从RMQ_SYS_TRANS_HALF_TOPIC获取到了消息

public OperationResult commitMessage(EndTransactionRequestHeader requestHeader) {
        return getHalfMessageByOffset(requestHeader.getCommitLogOffset());
    }
    
    
private OperationResult getHalfMessageByOffset(long commitLogOffset) {
        OperationResult response = new OperationResult();
        // 只看这一行就行,获取了一个MessageExt
        MessageExt messageExt = this.transactionalMessageBridge.lookMessageByOffset(commitLogOffset);
        if (messageExt != null) {
            response.setPrepareMessage(messageExt);
            response.setResponseCode(ResponseCode.SUCCESS);
        } else {
            response.setResponseCode(ResponseCode.SYSTEM_ERROR);
            response.setResponseRemark("Find prepared transaction message failed");
        }
        return response;
    }

这里记录代码2:endMessageTransaction

java 复制代码
private MessageExtBrokerInner endMessageTransaction(MessageExt msgExt) {
    // 这里就拿到真实的topic
    msgInner.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));
    ...
}

这里记录代码3:deletePrepareMessage

java 复制代码
private final ConcurrentHashMap<Integer, MessageQueueOpContext> deleteContext = new ConcurrentHashMap<>();

---
public class MessageQueueOpContext {
    // 阻塞队列
    private LinkedBlockingQueue<String> contextQueue;
}


public boolean deletePrepareMessage(MessageExt messageExt) {
    // <queueID:offset1,offset2,offset3>
    // deleteContext实际上是以队列ID(真实写入的topic的队列ID)作为key,queueOffset按照`,`作为分割符拼接而成的,这样就是每次处理一批消息而不是一条消息
    Integer queueId = messageExt.getQueueId();
    MessageQueueOpContext mqContext = deleteContext.get(queueId);
    ...
    // OFFSET_SEPARATOR = ","
    String data = messageExt.getQueueOffset() + TransactionalMessageUtil.OFFSET_SEPARATOR;
    boolean res = mqContext.getContextQueue().offer(data, 100, TimeUnit.MILLISECONDS);
    if (res) {
        int totalSize = mqContext.getTotalSize().addAndGet(data.length());
        if (totalSize > transactionalMessageBridge.getBrokerController().getBrokerConfig().getTransactionOpMsgMaxSize()) {
            // 这里是一个批处理,真实的处理deletePrepare的类transactionalOpBatchService,这里我们在代码4中分析transactionalOpBatchService的作用
            this.transactionalOpBatchService.wakeup();
        }
        return true;
    }
    ....
    下面的代码是在没有添加成功阻塞队列的一个容错处理

}

代码3: transactionalOpBatchService对应TransactionalOpBatchService类

java 复制代码
// 核心方法是TransactionalMessageServiceImpl类中的batchSendOpMessage方法
public long batchSendOpMessage() {
    ...
    // 这个方法里出现了RMQ_SYS_TRANS_OP_HALF_TOPIC,打了一个标记我们后面从dashboard的消息可以看到是d表示delete
    // return new Message(opTopic, TransactionalMessageUtil.REMOVE_TAG,sb.toString().getBytes(TransactionalMessageUtil.CHARSET));
    // 实际最终写入RMQ_SYS_TRANS_OP_HALF_TOPIC的消息体是每一条commit/rollback的消息的queueoffset按照`,`拼接
    Message opMsg = getOpMessage(entry.getKey(), null);
    // 写消息
    this.transactionalMessageBridge.writeOp(entry.getKey(), entry.getValue()
}

到这里整个事务消息的流程就已经梳理清楚了,我们先broker在收到生产者发过来的事务状态后做了:

  • 从RMQ_SYS_TRANS_HALF_TOPIC中获取到消息
  • 根据事务状态COMMIT/ROLLBACK选择是否将消息写入真实的topic,UNKNOW状态我们这里是不处理的
  • 将已经commit/rollback的消息的queueOffset作为消息体,写入到RMQ_SYS_TRANS_OP_HALF_TOPIC中,至此事务消息处理完毕

我们来简单画个图来整理这个过程

这里最终写入到RMQ_SYS_TRANS_OP_HALF_TOPIC中的方法其实是有着相同queueID的消息的queueOffset

我们最后在用一个例子来模拟一下RMQ_SYS_TRANS_HALF_TOPIC和RMQ_SYS_TRANS_OP_HALF_TOPIC中的数据形式

假设我们有两个两个队列的topic分别为topic1topic2,然后这两个topic一起写事务消息在第一阶段他们都会写入RMQ_SYS_TRANS_HALF_TOPIC中,这个时候他们在RMQ_SYS_TRANS_HALF_TOPIC中的形式应该是下图所示,红色方块代表Message。

这个时候他们会执行executeLocalTransaction,有些消息会被commit,有些会rollback,有些就会是UNKNOW(这种我们这里先不讨论),这里假设就是两种,commit or rollback,那么最终在topic1和topic2中的消息就有可能是这样的。

topic1中的消息都看起来都commit了

topic2中queueID=2的消息的少了queueOffset=2的那就说明这条消息的事务状态可能就是rollback的,当然这些都是我构造出来的。

最终RMQ_SYS_TRANS_OP_HALF_TOPIC中保存的消息的body可能如上图所示,我们可以看到他们能被放在一个Message中是因为他们的QueueID一致,和topic无关,保存的内容就是他们的QueueOffset

以上图解纯属个人理解。


checkLocalTransaction

在上文中我们好像都没有看到checkLocalTransaction方法的身影,这里就有疑问了,如果消息返回了UNKNOW,或者说执行中抛出了RunTimeException,executeLocalTransaction中返回的是null怎么办,这样就不会调用endMessageTransaction了,这里就是checkLocalTransaction发挥的时候,除了主动提交事务,还会有一个定时任务负责定时检查是否有事务消息需要处理!

具体的代码逻辑是在TransactionalMessageServiceImpl类的check方法,这里直接看代码实在是难以理解,所以我们设计两种场景,并且在check方法中加上一些日志来具体分析

场景一:exec中直接commit

执行完以后我得到了这样一个SendResult

ini 复制代码
SendResult [sendStatus=SEND_OK, msgId=7F00000133FC5C8DA9624CEB5ECD0000, offsetMsgId=null, messageQueue=MessageQueue [topic=tranTest, brokerName=192.168.0.103, queueId=4], queueOffset=94]

queueOffset=94, msgId=7F00000133FC5C8DA9624CEB5ECD0000

RMQ_SYS_TRANS_OP_HALF_TOPIC

我们能看到commit的消息成功写入了RMQ_SYS_TRANS_OP_HALF_TOPIC中,并且body中的QueueOffset是94

RMQ_SYS_TRANS_HALF_TOPIC

也没有什么问题,成功写入的,结合日志和代码分析

java 复制代码
public void check(long transactionTimeout, int transactionCheckMax,
        AbstractTransactionalMessageCheckListener listener) {
    // transactionTimeout=60 transactionCheckMax=15
        String topic = TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC;
        // 这里RMQ_SYS_TRANS_HALF_TOPIC就是一个单队列topic只有一个MessageQueue, for循环我就省略了
        Set<MessageQueue> msgQueues = transactionalMessageBridge.fetchMessageQueues(topic);
        // 获取到RMQ_SYS_TRANS_OP_HALF_TOPIC
        MessageQueue opQueue = getOpQueue(messageQueue);
        // 获取当前的Offset, 这里目前有一个疑惑的地方GroupName=CID_SYS_RMQ_TRANS,这个offset是什么时候更新的
        long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue);
        long opOffset = transactionalMessageBridge.fetchConsumeOffset(opQueue);
        
        // log.info("Before check, the queue={} msgOffset={} opOffset={}", messageQueue, halfOffset, opOffset);
        // 这里日志的值为msgOffset=94 opOffset=23
        List<Long> doneOpOffset = new ArrayList<>();
        HashMap<Long, Long> removeMap = new HashMap<>();
        HashMap<Long, HashSet<Long>> opMsgMap = new HashMap<Long, HashSet<Long>>();
        // 去RMQ_SYS_TRANS_OP_HALF_TOPIC 起始offset 23查消息
        PullResult pullResult = fillOpRemoveMap(removeMap, opQueue, opOffset, halfOffset, opMsgMap, doneOpOffset);
        // log.info("193 getMessageNullCount={} newOffset={} nextOpOffset={} removeMap={}, doneOpOffset={}, opMsgMap={}", getMessageNullCount, newOffset, nextOpOffset, removeMap, doneOpOffset, opMsgMap);
        i=halfOffset //94
        // 从RMQ_SYS_TRANS_OP_HALF_TOPIC中查到了offset=23的消息
        // getMessageNullCount=1 newOffset=94 nextOpOffset=24 removeMap={94=23}, doneOpOffset=[], opMsgMap={23=[94]}
        if (removeMap.containsKey(i)) {
            // 走到了这里的逻辑我们就不看else了,等会下面看else
            // log.info("Half offset {} has been committed/rolled back", i);
            Long removedOpOffset = removeMap.remove(i);
            opMsgMap.get(removedOpOffset).remove(i);
            if (opMsgMap.get(removedOpOffset).size() == 0) {
                opMsgMap.remove(removedOpOffset);
                doneOpOffset.add(removedOpOffset);
            }
        }
        ...一大堆的消息时间校验
        // 这里我们不需要执行check方法,因为我们已经在ophalf里查到了消息
        // 具体的判断需不需要执行check的逻辑如下
        List<MessageExt> opMsg = pullResult == null ? null : pullResult.getMsgFoundList();
        boolean isNeedCheck = opMsg == null && (valueOfCurrentMinusBorn > checkImmunityTime
                            || opMsg != null && opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout
                            || valueOfCurrentMinusBorn <= -1);
        // 下面这段感觉没有什么看的又去ophalf里查了一次,offset设置成了24,很明显应该是null
        // 这两行比较关键,newoffset此时是95了
        newOffset = i + 1;
        i++; 
  
        if (newOffset != halfOffset) {
        // 有一种豁然开朗的感觉,RMQ_SYS_TRANS_HALF_TOPIC的位点提交!!!下一次通过transactionalMessageBridge.fetchConsumeOffset取到的位点就是95了
            transactionalMessageBridge.updateConsumeOffset(messageQueue, newOffset);
        }
        // RMQ_SYS_TRANS_OP_HALF_TOPIC 位点提交!!下次通过transactionalMessageBridge.fetchConsumeOffset取到的位点是24
        long newOpOffset = calculateOpOffset(doneOpOffset, opOffset);
        if (newOpOffset != opOffset) {
            transactionalMessageBridge.updateConsumeOffset(opQueue, newOpOffset);
        }
        // 再往后又去这两个topic中根据新的位点查了一次消息,结果就是没有查到
}

至此!所有正常commit/Rollback的部分全部分析完毕! 在正常commit/checkLocalTransaction,并且会在check中提交RMQ_SYS_TRANS_HALF_TOPIC和RMQ_SYS_TRANS_OP_HALF_TOPIC的位点信息

场景二:executeLocalTransaction中返回UNKNOW

ini 复制代码
SendResult [sendStatus=SEND_OK, msgId=7F0000017EE66A6824BE4D2BB7330000, offsetMsgId=null, messageQueue=MessageQueue [topic=tranTest, brokerName=192.168.0.103, queueId=13], queueOffset=96]

What?! SendResult中返回的QueueOffset是96,但是最终保存到RMQ_SYS_TRANS_OP_HALF_TOPIC中的QueueOffset是97,我们带着这个疑问去看代码,我们首先先梳理下我们当前的信息

  • 消息成功commit了 (说明成功执行check方法了)
  • trantest中能够查到这条消息
  • 消息保存到RMQ_SYS_TRANS_OP_HALF_TOPIC中QueueOffset有增加

其实check方法上面我们已经了解的差不多了,我们就看看区别的地方

java 复制代码
long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue);
long opOffset = transactionalMessageBridge.fetchConsumeOffset(opQueue);
// 这里的pullRequest是null,因为此时消息还没有commit也就没有写入RMQ_SYS_TRANS_OP_HALF_TOPIC
PullResult pullResult = fillOpRemoveMap(removeMap, opQueue, opOffset, halfOffset, opMsgMap, doneOpOffset);

下面我们来看下上文中代码块24行那里说的else

java 复制代码
// i=96,getResult就不为null了
GetResult getResult = getHalfMsg(messageQueue, i);
// 此时就需要执行本地事务中的check方法了
if (isNeedCheck) {
// 这里重新把消息放回HalfMsgQueue,这里很有可能是QueueOffset增加的原因
// 确实transactionalMessageBridge.putMessageReturnResult(msgInner);这里最终重新向RMQ_SYS_TRANS_HALF_TOPIC中放了一条消息从下图中就可以看到
    if (!putBackHalfMsgQueue(msgExt, i)) {
        continue;
    }
    putInQueueCount++;
    // 执行本地check方法
    // 发送给客户端RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHECK_TRANSACTION_STATE, requestHeader);
    // 然后会执行doExecuteEndTransactionHook(msg, uniqueKey, brokerAddr, localTransactionState, true);
    listener.resolveHalfMsg(msgExt);
}

当执行doExecuteEndTransactionHook后剩下的就和上文中的commit/rollback一样了,如果在check中还返回了UNKNOW,那就会一直循环这个过程直到事务消息超时或者超过一定的次数

开发、运维过程中使用和排查事务消息

首先说下开发过程中吧,还是用上面的写DB和写RocketMQ的例子

scss 复制代码
transaction {
    updateDB()
    success = sendMessage()
    if success {
        commit()
    }
    rollback()
}

这里我们就可以先加上事务消息了,我们将这些操作移动到executeLocalTransaction方法中

java 复制代码
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
        // 需要执行的内容
        System.out.println("executeLocalTransaction");
        try {
            updateDB();
            putES();
            putLogFile();
        } catch Exception e {
            return LocalTransactionState.ROLLBACK;
        }
        return LocalTransactionState.COMMIT;
    }

上面的内容我又加了一些操作,这些操作都要是一个整体,这里其实已经能看出来问题了,我们的回滚方法只有RocketMQ具备,在RocketMQ回滚的时候别的方法实际上并没有回滚的能力,这里我们应该对每一个操作都要有回滚的方法。

java 复制代码
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
        // 需要执行的内容
        System.out.println("executeLocalTransaction");
        try {
            updateDB();
            putES();
            putLogFile();
        } catch Exception e {
            // rollback中针对DB,es,logfile都要能够回滚并且是幂等的
            rollback();
            return LocalTransactionState.ROLLBACK;
        }
        return LocalTransactionState.COMMIT;
    }
    

这个其实是我对于事务消息用于开发时的一个感想


再说说运维场景,主要就是需要查这个事务消息的状态,这个事务消息是commit还是rollback,有没有真实的写入到real topic中

这些都可以根据dashboard或者mqadmin去查到,具体遇到事务消息的运维场景还不多,后面可以补充 具体排查时可以查broker端的transaction.log文件,broker.log中其实也能反应一部分的问题,像我这次其实触发了一个问题

我在check方法还没有执行完的时候就把我的生产者shutdown了,导致broker.log里就能看到channel已经关闭了,但是broker还想调用本地的事务check方法

相关推荐
码上一元6 小时前
消息队列:如何确保消息不会丢失?
kafka·消息队列·rocketmq
bubble小拾1 天前
RocketMQ实战与集群架构详解
架构·rocketmq·java-rocketmq
拾木2003 天前
RocketMQ 消费方式
github·rocketmq·java-rocketmq
我真有起床气3 天前
如何在 Spring Boot 中实现 RocketMQ 的批量消息消费
spring boot·rocketmq·java-rocketmq
花开富贵..9 天前
RocketMQ安装与使用
spring boot·spring cloud·rocketmq
程序员小雷11 天前
字节面试 | 如何测试RocketMQ、RocketMQ?
测试工具·面试·职场和发展·单元测试·测试用例·rocketmq·postman
充值内卷12 天前
ASP.NET Core 入门教学八 集成RocketMQ消息队列
后端·asp.net·rocketmq
甜甜不甜100112 天前
消息中间件 --Kafka
分布式·kafka·rocketmq
lixiaoyi0113 天前
Rocketmq源码分析(1)
rocketmq
阿里云云原生13 天前
基于 RocketMQ 的云原生 MQTT 消息引擎设计
阿里云·云原生·rocketmq