從源碼分析RocketMQ系列-Producer的SendResult來自哪裏?

導語
  對於消息中間件大家都應該不陌生,現在比較主流的消息中間件有Kafka、RabbitMQ、RocketMQ、ActiveMQ等等。前段時間花了很長時間分析了關於RocketMQ源碼,之前也分享了關於RabbitMQ的相關知識。當然後期還會繼續分享,與此同時。將個人學習RocketMQ的相關的總結與大家一起分享。這個系列就是總結一下自己之前對於RocketMQ源碼的分析。


  首先博主選擇的是RocketMQ比較新的版本release-4.5.2。當然現在Git上面有比這個還要新的版本。有興趣的讀者可以從GitHub上面獲取最新的代碼進行閱讀。下面就開始進入到正題了。

  在中間件中一個最爲主要的概念就是消息生產者,也就是產生需要的消息的一方,當然這個消息生產者是一個比較廣泛的概念。當然所謂的消息也有很多的類型,例如序列化對象、JSON字符串、XML字符串等等。下面就來通過實例看看在RocketMQ中怎麼使用消息生產者的。

實例分析

初始化消息

  打開源碼之後,在org.apache.rocketmq.example.quickstart.Producer中,RocketMQ爲我們提供了一個生產者的小例子,如下。

/**
 * 這個類是教如何使用 DefaultMQProducer 進行發送消息的
 */
public class Producer {
    public static void main(String[] args) throws MQClientException, InterruptedException {

        /*
         * 實例化了一個Producer對象,並且制定的響應的group name:
         * 對於這個Group Name 是幹什麼的,在後面的分析中會慢慢看到
         */
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        /**
        * 設置了一個NameSpace;也就是名稱空間,類似於註冊中心一樣
        */
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        for (int i = 0; i < 10000; i++) {
        	//設置每兩秒發送一個消息,並且指定了Topic、和Tag、設置了消息體,並且制定UTF8編碼
            try {
                TimeUnit.SECONDS.sleep(2);
                Message msg = new Message("World" /* Topic */,
                        "TagA" /* Tag */,
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                );
                /*
                 * 調用發送消息到Broker中
                 */
                SendResult sendResult = producer.send(msg);

                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }

        /*
         * 以一種比較優雅的方式關閉Producer
         */
        producer.shutdown();
    }
}

  從上面的代碼來看,發送消息似乎是一個很簡單的東西,指定一個group name,指定一個Topic,設定好消息體之後就可以將消息發送到Broker中。實際上真的有那麼簡單麼,首先來分析一下Message類,org.apache.rocketmq.common.message.Message,也就是說通過這個例子進行消息發送的所有的數據都是由這個類來進行封裝的,並且從下面的構造函數中也可以發現一些問題


public Message(String topic, String tags, byte[] body) {
    this(topic, tags, "", 0, body, true);
}


public Message(String topic, String tags, String keys, int flag, byte[] body, boolean waitStoreMsgOK) {
    this.topic = topic;
    this.flag = flag;
    this.body = body;

    if (tags != null && tags.length() > 0)
        this.setTags(tags);
    if (keys != null && keys.length() > 0)
        this.setKeys(keys);

     this.setWaitStoreMsgOK(waitStoreMsgOK);
 }
 

  從下面兩個方法中可以看到,當一個Message被創建的時候其實是對MessageConst.PROPERTY_WAIT_STORE_MSG_OK屬性設置了一個值。這個屬性來自於org.apache.rocketmq.common.message.MessageConst常量類,提供了很多的字符常量


public void setWaitStoreMsgOK(boolean waitStoreMsgOK) {
   this.putProperty(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, Boolean.toString(waitStoreMsgOK));
 }
    
void putProperty(final String name, final String value) {
   if (null == this.properties) {
       this.properties = new HashMap<String, String>();
    }

    this.properties.put(name, value);
}

發送消息

  到這裏我們都不需要管上面這些對於Message的初始化操作都是什麼,繼續下面的邏輯就可以了,慢慢就會發現這些參數都用到了什麼地方。繼續往下會看到調用瞭如下的這樣一個方法


SendResult sendResult = producer.send(msg);

System.out.printf("%s%n", sendResult);

  看上去很不起眼的一個方法,並且還有一個返回對象。首先來看看這個對象是什麼通過下面這種方式打印出來的效果又是什麼。
org.apache.rocketmq.client.producer.SendResult 類

public class SendResult {
    private SendStatus sendStatus; //發送狀態
    private String msgId; //message id
    private MessageQueue messageQueue; //消息隊列
    private long queueOffset; //隊列偏移量
    private String transactionId; //追蹤ID
    private String offsetMsgId; //偏移下一個消息的ID
    private String regionId; // 區域id
    private boolean traceOn = true; //標識是否可追蹤
    

打印效果是由重寫的這個toString方法來決定的。當然這個toString方法是可以繼續擴展的。這裏瞭解這些之後,就可以繼續看看後面的邏輯了。

@Override
public String toString() {
   return "SendResult [sendStatus=" + sendStatus + ", msgId=" + msgId + ", offsetMsgId=" + offsetMsgId + ", messageQueue=" + messageQueue
            + ", queueOffset=" + queueOffset + "]";
}

消息邏輯詳細分析

  看到其實他是調用了producer對象的send()方法。發送消息,我們可以繼續追蹤這個方法看看後續的邏輯,會看到其實這個方法是通過繼承過來的,是實現了org.apache.rocketmq.client.producer.MQProducer 接口,對於接口,在Java中其實就是提供了一些規範。繼承接口的類就需要實現接口裏面的方法。這裏先不需要管org.apache.rocketmq.client.producer.MQProducer接口提供了那些方法,先來看看下面這個方法的具體實現。

@Override
public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
	//消息檢車
    Validators.checkMessage(msg, this);
    //設置消息Topic
    msg.setTopic(withNamespace(msg.getTopic()));
    return this.defaultMQProducerImpl.send(msg);
}

  首先進入之後進行了一個消息的檢查,第二步,設置了msg的Topic。這裏爲什麼會有這樣的一個操作呢?這就要看看withNamespace(msg.getTopic())這個方法爲我們返回的返回值是什麼樣子的?很多人會發現這個方法似乎是從自己本身調用的,也沒有this關鍵字作爲標識。但是點擊進入之後,會發現這個方法其實來自於這個類org.apache.rocketmq.client.ClientConfig,見名知意。就是客戶端的一個配置類。這個方法的意思就是對於Namespace進行了封裝,也就是說Namespace中需要保證在Namespace進行管理的時候,確實有需要的信息在Namespace中。而ClientConfig類就是也是作爲客戶端的一個基礎配置類存在。

public String withNamespace(String resource) {
   return NamespaceUtil.wrapNamespace(this.getNamespace(), resource);
}


public String getNamespace() {
    if (StringUtils.isNotEmpty(namespace)) {
        return namespace;
    }
   if (StringUtils.isNotEmpty(this.namesrvAddr)) {
        if (NameServerAddressUtils.validateInstanceEndpoint(namesrvAddr)) {
            return NameServerAddressUtils.parseInstanceIdFromEndpoint(namesrvAddr);
        }
    }
    return namespace;
}
    

開始發送消息了

   做好前面的準備工作之後就進入到了最爲關鍵的地方了,似乎這個方法纔是最關鍵的方法,它還調用了this.defaultMQProducerImpl的方法。那麼我們之前的DefaultMQProducer 對象又是什麼呢?,第二行代碼似乎給出了答案,並且發現 public class DefaultMQProducerImpl implements MQProducerInner 這個類居然不是 DefaultMQProducer的子類,而且還有public class DefaultMQProducer extends ClientConfig implements MQProducer,擔心的事情終於出現了,原來這兩個類一點關係都沒有。那就繼續往下看看。

return this.defaultMQProducerImpl.send(msg);
 
protected final transient DefaultMQProducerImpl defaultMQProducerImpl;

  繼續往下分析,會看到如下的一段代碼,更爲奇特的是它上面有個註釋。這個註釋應該不默認,同步

/**
* DEFAULT SYNC -------------------------------------------------------
*/
public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
  return send(msg, this.defaultMQProducer.getSendMsgTimeout());
}

  到這裏會發現,我們得調用終於隨着正主了。到了最爲關鍵的發送代碼了,結果你點擊進去之後又到了另外一個方法中

public SendResult send(Message msg,long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
}
       

 &emsp到這裏纔算是真的找到歸宿了,這裏似乎是找到真正需要的代碼,下面就來進一步的分析this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout) 方法中的代碼

 private SendResult sendDefaultImpl(
        Message msg,
        final CommunicationMode communicationMode,
        final SendCallback sendCallback,
        final long timeout
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException
    

  首先會看到這個方法是使用了this關鍵字,所以說明這個方法其實就是上面這個類對象所屬的方法,也就是說他是org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl 這類中的一個方法,在看看傳入的參數

  • Message 對象,這個對象之前說過的就是封裝了傳遞過程中需要傳遞什麼樣的參數來實現的。
  • CommunicationMode 交換模式,這裏RocketMQ提供了三種模式,SYNC,ASYNC,ONEWAY。同步、異步、單向。對於這三種模式,在後面的分享中還會詳細說到這裏只是簡單的提出一個概念。
  • SendCallback:發送回調接口,這個接口提供了兩個方法,一個是發送成功之後回調,一個是發送有異常回調。
  • timeout:超時時間,在調用過程中需要設置一個調用超時時間。

  看完傳遞了那些參數之後接下來就需要進入關鍵點了,但是在發送消息之前做了一些準備性的工作這裏來看看這些準備性的工作有那些。

 Validators.checkMessage(msg, this.defaultMQProducer);

final long invokeID = random.nextLong();
long beginTimestampFirst = System.currentTimeMillis();
long beginTimestampPrev = beginTimestampFirst;
long endTimestamp = beginTimestampFirst;
TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());

  從上面代碼中會看到其實也沒有做太多的準備工作,就是先準備了一些初始化計算的時間,並且準備了一個TopicPublishInfo 對象,而且這個對象還是通過msg中的Topic進行獲取的。獲取完成之後也就是做了一切開始準備操作的工作,就進入到了正確的流程中

if (topicPublishInfo != null && topicPublishInfo.ok()) {
	////代碼暫時省略           
}

  if條件判斷裏面的中的代碼如下所示,開始的時候就準備了是否超時回調、消息隊列、異常信息、返回結果。四個對象。其實這裏最值的注意的就是timesTotal變量,這個變量表示,重試次數,在前面的提到過這裏使用的同步模式,如果使用的是同步模式this.defaultMQProducer.getRetryTimesWhenSendFailed()這個函數返回的屬性值是2,前面還有一個加 1 的操作,所以說timesTotal變量最後的值是3,也就是說在同步模式下,需要發送三次消息。那麼是怎麼發送的呢。

 &emps;繼續往下會看到String[] brokersSent = new String[timesTotal]; 創建了一個長度爲timesTotal的數組,並且後續看到傳入的參數是,獲取到的broker列表,在一般使用配置的時候broker都是以字符串的形式在配置文件中利用逗號分隔的,所以說這裏要將獲取的brokerSent 設置爲一個字符串類型的數組。

boolean callTimeout = false;
MessageQueue mq = null;
Exception exception = null;
SendResult sendResult = null;

int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;

int times = 0;
String[] brokersSent = new String[timesTotal];
for (; times < timesTotal; times++) {
     String lastBrokerName = null == mq ? null : mq.getBrokerName();
     MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
     if (mqSelected != null) {
         mq = mqSelected;
         brokersSent[times] = mq.getBrokerName();

		//後續代碼待接入

  String lastBrokerName = null == mq ? null : mq.getBrokerName();這個是一個關鍵性的代碼,爲什麼說它關鍵呢?首先第一次進入到這個循環的時候mq確實是一個null,也就是說條件滿足,對於三元運算符來說條件滿足的時候應該返回的是第一個值,也就是最後lastBrokerName的值是null。進入到後續邏輯中也就是selectOneMessageQueue(topicPublishInfo, lastBrokerName) 方法,那麼下面就來看看這個方法當lastBrokerName爲null的時候是如何操作的。

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
    return this.mqFaultStrategy.selectOneMessageQueue(tpInfo, lastBrokerName);
}

  最終進入到了如下的一個方法中,第一個行代碼上來就是分析是否是發送失誤的策略,可想,當sendLatencyFaultEnable參數爲true的時候這個if判斷纔會執行,否則是不會執行的。直接就 return tpInfo.selectOneMessageQueue(lastBrokerName); 可見,第一次進來之後策略是如何配置的就是如何操作的。這裏先來看看默認爲false的情況。
在這裏插入圖片描述
  this.sendLatencyFaultEnable這個值默認是false,進入之後進入的是,下面return語句,而return語句的邏輯如下圖所示
在這裏插入圖片描述
  方法中調用的index的計算策略如下圖所示,對於ThreadLocal有了解的人都知道,這是屬於線程獨有的東西。也是一個隨機產生的隨機數。並且找到它的絕對值。當然對於這些算法先不進行深入研究這裏主要是關注,上面這段代碼中其實默認返回的是一個MessageQueue。而這個MessageQueue是從MessageQueueList通過某種算法進行查找的。如果lastBrokerName爲null的時候是直接進行查找,如果不是,則通過算法避免了一些衝突。
在這裏插入圖片描述

  到這裏就真正瞭解了這個MessageQueue是怎麼選入的,選入之後就進入了一個trycatch語句中。由於代碼格式的關係,後續的代碼都是以圖片的形式爲大家展示,
在這裏插入圖片描述
  會看到在進入之前先來判斷了一下是否有選中的MessageQueue,並且獲取到了第一次發送的BrokerName。調用了msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic())); 方法,這裏會看到有一個判斷的條件是times大於0,那麼是否還存在這個times小於零的時候呢?這裏有一點需要注意就是,在進入第一次循環的時候times初始化是0,在編程中也是數組的下標也是從0 開始,那麼在第一次進行調用的時候並沒有執行上面這條語句,而是在後續重試的機制中才會調用對應的setTopic代碼。

  在前面分析準備工作的時候提到了一個sendResult ,從代碼中可以看到,這個參數的值是由this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);方法提供的,也就是這個方法纔是整個發送過程中最爲核心的代碼。有了這個結果之後,就需要找在什麼時候進行返回了,什麼時候進行返回操作,纔是真正程序所需要的。繼續往下,會經歷一個this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);更新失敗項的操作。執行完這一步之後,就要開始真正的返回工作了。

數據返回工作

  到這裏假設所有的工作都完成了,那麼將會有一個數據返回的操作,返回我們需要的SendResult。會發現在在下面代碼中,有一個模式選擇,如果沒有找到對應的模式則返回爲空,如果找到對應的模式則返回一個sendResult的對象。否則就是break;這裏需要注意,到目前爲止上面所有的分析邏輯都還在第一次的for循環中。也就是說在判斷模式的時候是最爲重要的。那麼按照代碼邏輯分析來看,會從異步模式、單向模式、一直判斷到同步模式,在同步模式的時候進入到了一個sendResult的狀態判斷中,判斷之後進入到了第二判斷中。那麼這兩次判斷分別是什麼呢?
在這裏插入圖片描述
  從代碼的層面來理解,發送是否成功,當不成功的時候是否使用其他的Broker嘗試發送。如果兩個條件都滿足,這進入到了continue中,也就是繼續進行下次循環。這個下次循環就是前面提到的for循環,那麼只需要一個第一次發送成功之後就不需要進行後續循環了麼?從代碼的意思是是直接就return了。那就繼續往下看。
  除了上面的代碼中有return語句之外還有其他的地方有return語句,在Catch MQBrokerException異常時候會返回一個sendResult。
在這裏插入圖片描述
  在所有的三次嘗試都完成之後如果沒有對應的操作,就返回一個。也許有人會問,之前那個地方不是已經判斷過一次了麼,既然三次都重試失敗了,那就應該沒有返回結果呀!仔細分析會知道有異常並不代表消息沒有發送,狀態是失敗,說明的是消息沒有被正常發送,並不是說沒有消息。這些都是在集羣場景下才會出現的問題。
在這裏插入圖片描述

  下面是觸發異常場景之後得到的異常信息,而這個異常信息的觸發來自與下面這樣一段代碼
在這裏插入圖片描述

異常代碼邏輯,會看到當執行完所有的發送語句之後,如果還沒有收到sendResult,或者是這Result是空的時候,就會觸發下面這段異常。最終返回就是這段異常。
在這裏插入圖片描述

核心發送邏輯

  到這裏對於整體的接收消息和返回邏輯都已經有了大致的瞭解,但是有一個方法始終沒有提到。那就是核心發送邏輯this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);,這個方法纔是真正返回我們所需要的sendResult的方法,那麼這個方法到底長什麼樣子呢?下面就來一起分析分析。

private SendResult sendKernelImpl(final Message msg,
                                 final MessageQueue mq,
                                 final CommunicationMode communicationMode,
                                 final SendCallback sendCallback,
                                 final TopicPublishInfo topicPublishInfo,
                                 final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException
                                 

  也看到了這個方法也指定了一些參數,在使用的時候也指定了一些調用的邏輯以及初始化的數據。下面就來介紹一下參數

  • Message : 也就是之前提到的封裝好的數據信息
  • MessageQueue: 通過上面的代碼分析可以知道是通過一種簡單的選擇算法進行選中的一個消息隊列
  • CommunicationMode:消息發送模式,也就是之前提到的同步、異步、單向
  • SendCallBack: 回調對象,其中有兩個方法一個是成功回調,一個異常回調
  • TopicPublishInfo:發佈Topic信息對象
  • timeout:超時時間。

  簡單說明:在之前的調用中Debug的時候發現一個問題,RockMQ之所以可以保證高效的消息發送機制,其實在裏面有一點點的小小的技巧在其中,這個小技巧就是這個超時時間。這個超時時間在一些場景下可以保證消息的高效傳遞。

準備工作

  在介紹完了上面的方法參數之後下面就來說說關於,實際操作前的準備工作,會發現還是離不開時間的調用。首先需要獲取到的就是進行Broker地址的獲取,如果沒有獲取到則嘗試重新拉取然後再次獲取這種方式,最終目的就是爲了獲取到這Broker地址

long beginStartTime = System.currentTimeMillis();
String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
if (null == brokerAddr) {
    tryToFindTopicPublishInfo(mq.getTopic());
    brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
}

正常流程

  由於後續代碼太多還是採用分段截圖的方式進行說明。上內容強調了對於Broker地址的獲取,到這裏是在是太重要了,發現後續邏輯代碼第一步就是對這個Broker地址進行了檢查。對於這個檢查邏輯就先不展示了,就是用來某種系統屬性的方式對IP列表進行操作,看看是否是屬性標識的。默認是false。
在這裏插入圖片描述
  檢查完成之後就進入到了一個大的try cache語句中,在這之前還將Message變成了一個二進制的數組。這個也就是在日誌中經常見到的消息體變成了ASCII碼。
在這裏插入圖片描述
  會看到上來就先進行判斷是否是批處理消息,如果是批處理消息則設置一個UniqID,也就是在消息中看到UNIQ_KEY,那麼問題來了MessageBatch東西到底是什麼呢?爲什麼客戶端的消息中會有這個東西。這裏由於是分析消息流向,這裏就先不說明,到後面的內容中再進行說明;第二個判斷是,獲取到配置中的Namespace,通過獲取到的客戶端配置將這個配置作爲 InstanceID 設置到Message中,設置topic標識;第三個判斷,調用壓縮消息的方法,並且這裏有個標識,就是是否壓縮標識,並且這裏有個一個需要注意的操作 sysFlag |= MessageSysFlag.COMPRESSED_FLAG; 爲什麼這裏要做這個操作呢?這裏簡單的說說,這個操作符的意思是按位或,什麼是按位或呢?就是說將所表示的數據按照二進制展開,相同位置進行或運算。有1 則就是1 沒有1 就是0。而MessageSysFlag.COMPRESSED_FLAG 是 0x1 也就是說十六進制的1。如果進行了壓縮之後sysFlag的值就是1 了。第四個判斷, 這裏會看到首先獲取了一個TRAN_MSG 屬性,當不爲空的情況下對sysFlag進行了新的規則校驗,這個校驗 0x1 << 2 表示什麼意思呢,<< 表示左移,也就是說原來的數據乘以2的幾次方。這裏就變成了4,由於原來的數據是按位或。所以這個時候sysFlag的值就會變成5。這個值在後續的分析中會有用到。
  分析完上面四個判斷之後簡單的總結就是判斷是否是指定的Topic,判斷數據是否壓縮,判斷tranMsg並且修改了一個sysFlag,似乎也沒有與到其他的問題。繼續往下看就知道這些判斷都是什麼意思了
在這裏插入圖片描述
  從圖中的代碼可以看到,做完上面四個判斷之後,第五個判斷上來就先來了檢查,那麼這個檢查到底是什麼呢?看到下面代碼就會知道就是到禁用的Hook列表中進行了一個判斷,如果這個結果返回爲True。就會進入到這個代碼中,那麼下面來分析一下進入的這段代碼都幹了些什麼事情?

 public boolean hasCheckForbiddenHook() {
     return !checkForbiddenHookList.isEmpty();
 }
 
 

 &emps; 創建了一個CheckForbiddenContext 上下文的對象,並且完成之後會將這個上下文傳入到一個Hook中進行執行。接下來會看到另一個判斷這個判斷引入的是this.hasSendMessageHook(),也就是說這個方法一定是對象本身提供的,也就是這個對象org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl 。

public boolean hasSendMessageHook() {
    return !this.sendMessageHookList.isEmpty();
}

在這裏插入圖片描述
  這個也是判斷了需要操作的HookList中是否有對應的Hook。從上面的兩個Hook中可以看到,似乎提供的是一個動態Hook的機制,有多少以父類爲接口的Hook只要被加載到列表中就會進行Hook的操作。這裏的這些Hook就是爲了追蹤Message的流向,RocketMQ有一個admin模塊還commond命令模塊,就是提供這裏使用的。
在這裏插入圖片描述
  封裝完成Hook之後就開始進入到正常的Message的封裝。上面代碼封裝了一個SendMessageRequestHeader,從字面的意思是發送消息的請求頭。要支持一個協議,通常在請求頭中規定了一些公共的約定,這些約的對於所有的消息發送接收都是適用的。而這其中需要重點注意的就是一個之前說過的標識sysFlag,這個標識會隨着操作進行改變,還有這裏需要注意另外的一個標識UnitMode單元模式,這個需要注意一下,後續的分享中都會遇到。這裏還需要注意的一點,就是對於這些配置屬性的操作。
在這裏插入圖片描述
  封裝完請求頭之後,發現還是沒有知道到真正調用發送的方法在什麼地方。繼續往後會看到就是對SendResult的封裝了。接下來就來分析一下。
  在上面的分析中提到了一個交流模式,在發送的時候第一步就是對着模式的選擇,在外部方法中判斷交流模式是同步模式,異步模式和單向模式都是爲null的操作。也就是說能進入到核心發送方法的請求都是以同步模式進入的。但是分析上面的代碼會看到,其實在異步和同步case中都有很多的處理邏輯。而且最終都調用了一個方法 this.mQClientFactory.getMQClientAPIImpl().sendMessage(),這個方法對於SendResult進行了封裝響應。
  到這裏終於找到了sendResult是怎麼被響應回去的,看上去在Broker中流轉的Message,要經過很長時間的發展分裝進化,纔可以真正被送到Broker中進行流轉,有了這些標識之後才能更好的跟蹤消息。

總結

  到這裏,並沒有真正找到SendResult是怎麼被創建的,但是至少到這一步,找到了需要獲取SendResult需要哪些準備工作,下一篇博客繼續來分析來尋找SendResult到底從什麼地方被進行了封裝。

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