rocketmq如何實現分佈式事務

人工智能,零基礎入門!http://www.captainbed.net/inner

如果同一個數據源在本地事物很好控制,但是在不斷髮展的互聯網環境下,微服務越來越流行,這個時候,需要解決分佈式事物,需要保證數據的最終一致性。
所謂分佈式事務(全局事物),就是在兩個不同的系統中(即兩個不同的數據源,其實同一個數據源也行),無法同一個同一個spring事物去控制不同系統的事物的整體成功或者整體失敗。

分佈式事務的解決方法很多,但是性能和複雜性不一樣,今天說說rocketmq如何保證分佈式事務的。

rocketmq爲我們提供TransactionMQProducer API的支持。這個api在發送消息的時候主要做了三件事:
1,先發送需要發送的消息到消息中間件broker,並獲取到該message的transactionId。在第一次發送的時候,該消息的狀態爲LocalTransactionState.UNKNOW
2,處理本地事物。
3,根據本地事物的執行結果,結合transactionId,找到該消息的位置,在mq中標誌該消息的最終處理結果。
整體的執行源碼邏輯如下:


public TransactionSendResult sendMessageInTransaction(Message msg, LocalTransactionExecuter tranExecuter, Object arg) throws MQClientException {
        if (null == tranExecuter) {
            throw new MQClientException("tranExecutor is null", (Throwable)null);
        } else {
            Validators.checkMessage(msg, this.defaultMQProducer);
            SendResult sendResult = null;
            MessageAccessor.putProperty(msg, "TRAN_MSG", "true");
            MessageAccessor.putProperty(msg, "PGROUP", this.defaultMQProducer.getProducerGroup());

            try {
                //這裏執行第一次發送消息,也就是預發送,並獲取sendResult,這裏包含msg的所有消息
                sendResult = this.send(msg);
            } catch (Exception var10) {
                throw new MQClientException("send message Exception", var10);
            }

            LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
            Throwable localException = null;
            //根據預發送消息的狀態做不同的處理,這裏主要看SEND_OK
            switch(sendResult.getSendStatus()) {
            case SEND_OK:
                try {
                    if (sendResult.getTransactionId() != null) {
                        msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
                    }

// 這裏做第二步,執行業務邏輯,即本地事物,
//具體的本地事物在LocalTransactionExecuter參數的實現類中,
//需要根據自己的業務邏輯去寫,下面的//tranExecuter.executeLocalTransactionBranch(msg, arg);會執行實
//現類中的executeLocalTransactionBranch業務。
                    localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg);
                    if (null == localTransactionState) {
                        localTransactionState = LocalTransactionState.UNKNOW;
                    }

                    if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
                        this.log.info("executeLocalTransactionBranch return {}", localTransactionState);
                        this.log.info(msg.toString());
                    }
                } catch (Throwable var9) {
                    this.log.info("executeLocalTransactionBranch exception", var9);
                    this.log.info(msg.toString());
                    localException = var9;
                }
                break;
            case FLUSH_DISK_TIMEOUT:
            case FLUSH_SLAVE_TIMEOUT:
            case SLAVE_NOT_AVAILABLE:
                localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
            }

            try {
// 這裏的方法,其中的localTransactionState是第二次執行業務邏輯的結果
//可以根據這個結果,知道本地事物執行的成功還是失敗。或者是異常localException,
//這樣可以根據第一次發送消息的結果sendResult,去修改mq中第一次發送消息的狀態,完成第三步操作。
                this.endTransaction(sendResult, localTransactionState, localException);
            } catch (Exception var8) {
                this.log.warn("local transaction execute " + localTransactionState + ", but end broker transaction failed", var8);
            }

            TransactionSendResult transactionSendResult = new TransactionSendResult();
            transactionSendResult.setSendStatus(sendResult.getSendStatus());
            transactionSendResult.setMessageQueue(sendResult.getMessageQueue());
            transactionSendResult.setMsgId(sendResult.getMsgId());
            transactionSendResult.setQueueOffset(sendResult.getQueueOffset());
            transactionSendResult.setTransactionId(sendResult.getTransactionId());
            transactionSendResult.setLocalTransactionState(localTransactionState);
            return transactionSendResult;
        }
    }

上述第三步執行的方法endTransaction,邏輯如下:


 public void endTransaction(SendResult sendResult, LocalTransactionState localTransactionState, Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {

// 獲取第一次發送消息的id
        MessageId id;
        if (sendResult.getOffsetMsgId() != null) {
            id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId());
        } else {
            id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
        }

    //獲取事物id
        String transactionId = sendResult.getTransactionId();
        String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName());
        EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
        requestHeader.setTransactionId(transactionId);
        requestHeader.setCommitLogOffset(id.getOffset());

       //根據本地事物執行狀態localTransactionState,告知mq修改狀態

        switch(localTransactionState) {
        case COMMIT_MESSAGE:
            requestHeader.setCommitOrRollback(Integer.valueOf(8));
            break;
        case ROLLBACK_MESSAGE:
            requestHeader.setCommitOrRollback(Integer.valueOf(12));
            break;
        case UNKNOW:
            requestHeader.setCommitOrRollback(Integer.valueOf(0));
        }

        requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
        requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
        requestHeader.setMsgId(sendResult.getMsgId());
        String remark = localException != null ? "executeLocalTransactionBranch exception: " + localException.toString() : null;
//具體執行第三步完成整個事務。      
  this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark, (long)this.defaultMQProducer.getSendMsgTimeout());
    }

上述:
如果第三階段出現異常或者網絡原因,就是本地事務執行成功持久化到數據庫中,但是在修改mq中消息狀態出現異常的時候,這樣就可以出現本地和mq的消息狀態的不一致問題。或者說,所有的數據不一致問題。rocketmq都會定期通過TransactionMQProducer API初始化的時候,設置的TransactionCheckListener的的實現類的checkLocalTransactionState 方法檢查本地消息的狀態,根據本地狀態修改mq的狀態

package org.apache.rocketmq.client.producer;

import org.apache.rocketmq.common.message.MessageExt;

public interface TransactionCheckListener {
    LocalTransactionState checkLocalTransactionState(MessageExt var1);
}

這樣就可以確保生產端的數據要麼是本地事物執行失敗,rollback mq中消息,或者本地事物執行成功,mq中的消息也是待消費的狀態。

說到這裏,其實分佈式事務已經完成90%。如果生產端的的第三部不是成功狀態,消費端是不會消費第一階段的消息的。所以,只要消費端消費的消息,肯定是生產端成功的消息,而消費端只要保證冪等性,就可以準確的消費消息(因爲消費失敗了,mq是可以重試,如果最總都沒有被消費,基本是程序有bug,可以事後人工處理了。)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章