RocketMQ源碼分析之消費者

RocketMQ源碼分析我們主要從NameSrv、路由、生產者、消費者、消息存儲等方面一點點分析,本章主要講的是消費者的源碼分析。

一、前提

消費者消費分爲兩種模式,集羣模式和廣播模式,默認開啓的是集羣模式,集羣模式下同一個消費組中只能有一個消費者消費某個topic在broker中的隊列。廣播模式下,所有消費者都可以消費topic的信息。

消費者獲取消息的方式也有兩種,一個是主動從broker中pull消息,另一個是broker主動push消息給消費者。其實broker主動push消息給消費者,說到底還是消費者先發送請求給broker,表明自己需要哪些topic的消息,然後broker定期掃描,然後收到該topic的消息再發送給消費者。

二、消息的拉取

默認使用的是DefaultMQPushConsumer類,處於org.apache.rocketmq.client.consumer包下,該類繼承ClientConfig配置文件類實現MQPushConsumer接口,MQPushConsumer接口繼承MQConsumer接口,MQConsumer接口繼承MQAdmin接口,即DefaultMQPushConsumer--》MQConsumer--》MQAdmin。

通過實現的關係,可以發現無論生產者還是消費最後都是實現了MQAdmin接口,因此MQAdmin接口中都是跟topic相關的方法,MQConsumer接口中就是三個方法,一個是通過topic從消費者本地緩存獲取消息隊列,另外兩個都是消費消息失敗以後給broker的反饋。因此跟消費者啓動、銷燬以及消費有關的大部分方法都是在MQPushConsumer接口中。

1、啓動消費者

還是先從消費者啓動開始看,消費者啓動的方法在DefaultMQPushConsumer的start()方法。

啓動類中,先是檢查一些參數的合法性,例如消費者名之類的。然後就是根據topic以及其相對應的表達式,構建當前消費者訂閱的topic的信息,然後就是集羣模式下將當前進程id設置爲消費者實例的名稱,再就是通過MQClient管理器創建一個MQClient用於跟broker交互。其實發現這些流程跟生產者的啓動流程沒有什麼區別,就是多個一個構建訂閱的topic的相關信息。 咱們接下來就是根據啓動類中的步驟一個個分析。

 2、參數檢查

檢查一些常規參數是否爲空,消費者組、消費模式等等,這個就不細說了。

 3、構建訂閱的topic的信息

根據tags的過濾表達式來構建消費者訂閱的不同topic的相關信息。

4、 集羣模式下將當前進程id設置爲消費者實例的名稱

5、MQClient管理器創建一個MQClient

6、設置負載均衡策略

消費者的負載均衡策略很多,但是最常用還是以下兩個。例如一共有三個broker,其中前兩個都是有3個消息隊列,最後一個有2個消息隊列,這樣就有8個消息隊列,而消費者組中若有3個消費者。那麼基於同一個消費者組下的消費者不可消費同一個topic下的同一個消息,那麼就會有以下兩種複雜均衡策略。

           

注:因爲同一個消費者組下的消費者不能消費同一個topic下的同一個消息,因此,如果消費者的數量大於消息隊列的個數,那麼就有消費者沒有消息可消費。例如上面有8個隊列,但是有10個消費者,那麼就註定有消費者沒有消息可消費了。

7、讀取上次消費消息的偏移量

若是廣播模式,則消息的偏移量存儲在本地,如果是集羣模式,則消息的偏移量存儲在遠程。

若是廣播模式,則根據解析出來的路徑,讀取本地存儲偏移量的json,然後將messagequeue對應偏移量存儲在ConcurrentMap

表中。

 

若是集羣模式,本地並沒有做額外的操作,broker收到請求以後會查詢broker端存儲的偏移量,然後做處理的,後面再說broker端的。

8、啓動消費消息的服務

若是順序消費,則初始化一個順序消費消費的服務,然後啓動,如果是併發消費消息,則初始化一個併發消費消息的服務,然後再啓動。實現ConsumeMessageService接口的有兩個,一個是順序消息的,一個是併發消息的服務。

 

9、將當前消費者註冊到MQClient實例中

10、啓動

啓動的時候加了同步鎖,啓動的程序中啓動的東西也不少,咱們一個個將,獲取namesrv的地址咱們就不說了。 

11、this.mQClientAPIImpl.start(),啓動一個nettyClient跟broker交互。

12、this.startScheduledTask(),啓動各種定時任務,定時獲取namesrv地址,定時更新topic路由信息,定時發送心跳,定時持久化消費的偏移量。

13、先看這個,this.rebalanceService.start(),RebalanceService繼承了Thread,所以看其啓動的方法,主要是做了重新分發,具體怎麼做的就不講了,可以自己看看。

14、這個是重點,消息的拉取,this.pullMessageService.start(),PullMessageService繼承了Thread,所以看其啓動的方法,從拉取請求隊列中獲取一個拉取請求。

從拉去請求中獲得消費者組然後獲得消費者,然後impl.pullMessage(pullRequest);開始拉取消息 

 拉取方法中首先獲取的就是ProcessQueue,我解釋解釋這個類,這個類可以理解爲消息的中轉站,拉取的消息都扔在這個隊列中,然後被消費。如果這個隊列被拋棄了,那就不用繼續走下去了。

 

 

消息做流控,消息數量大於1000,或者消息大小大於100M則不再拉取 

拿到訂閱主題的相關信息

 接下來就是拉取消息的回調,消息還沒拉取到,先不看,等拉取以後咱們再回來看

構建系統拉取標識,標識哪個節點進行的拉取 

然後是消息的拉取,將回調函數都標明

根據brokerName獲取broker的地址,然後根據topic更新topic的路由信息 

然後就是封裝請求頭,然後發起請求,到最後還是一個netty客戶端發起請求。

15、Broker組裝消息並反饋

Broker對於拉取的消息做出反饋的類是org.apache.rocketmq.broker.processor包下的PullMessageProcessor類的processRequest()方法。

 該方法中,前面大部分都是校驗參數,咱們就不細說了

構建消息過濾的實體類,爲後期過濾消息做準備 

 從MessageStore獲取訂閱的消息,然後構建反饋頭準備反饋,接下來就是設置各種響應碼。

 如果從從節點讀取太慢,建議下次從主節點讀取。

16、下面就是消費者收到broker的反饋了,我們直接看上面說的那個回調函數,PullCallback

消息被分裝成pullResult

看到了吧,在這一步,消息被放到了processQueue中,然後processQueue又被給提交到消費者的消費線程池中。 

 

三、消息的推送

其實RocketMQ並沒有實現推送,消息的推送其實還是基於消息的拉取。RocketMQ有個長輪詢機制,每隔5s查詢一次,看是否有自己需要的消息。但是這種效率不高,因此就有了另一個機制,在消息達到broker時進行存儲的時候,監聽器監控到新消息以後喚醒pull請求。

該方法在org.apache.rocketmq.store下的DefaultMessageStore.start()方法中。

在doReput()方法中有以下代碼,如果不是從節點,意味着是主節點,代表讀取到了數據,那麼就會觸發消息到達的監聽器。 

然後拉取消息的服務就會被喚醒,然後處理消息,有興趣的可以自己看 

四、併發消息和順序消息

前面講了consumer發送拉取消息的請求給broker,然後發送請求的時候還有一個消息的回調PullCallback,在PullCallback中把消息從封裝的pullResult中拿出來放到了processQueue,然後再把processQueue交給consumeMessageService處理,其實就是在這裏對消息進行了處理。

consumeMessageService是一個接口,它的實現類有兩個ConsumeMessageConcurrentlyService和ConsumeMessageOrderlyService,分別是對順序消息和併發消息的處理,我們一個個來講。

1、併發消息(ConsumeMessageConcurrentlyService的submitConsumeRequest方法)

將消息傳遞過去,如果消息的條數小於32條,則將消息直接扔給消費者線程池。如果消息大於32條,那麼就會分頁,每頁32條,然後一次交給線程池一頁。

 

然後就看線程ConsumeRequest的run()方法,這裏是ConsumeMessageConcurrentlyService中的內部類ConsumeRequest。如果processQueue被拋棄,那就不用走下去了,消息都在裏面。再接着就是將消息交給消費的監聽器。

如果之前設置了鉤子函數,那麼接下來就執行鉤子函數的方法 

 

接下來就是將消息交給listen來繼續處理了。

 

2、順序消息(ConsumeMessageOrderlyService的submitConsumeRequest方法)

順序消費一般都是局部順序消息,都是對某個隊列進行消息的順序消費,因此不用擔心消息的條數過多。

 然後就看線程ConsumeRequest的run()方法,這裏是ConsumeMessageOrderlyService中的內部類ConsumeRequest。如果processQueue被拋棄,那就不用走下去了,消息都在裏面。

接下來就是順序消息和併發消息的區別,順序消息這裏使用了鎖,對消息的處理流程加鎖,只能一個個消費,至於對消息的處理還是跟併發消息的流程一樣,主要是加了鎖。

 

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