源碼目錄結構介紹&Remoting通信層
一:源碼目錄結構介紹
RocketMQ源碼分爲以下幾個package:
rocketmq-broker
:整個mq的核心,他能夠接受producer和consumer的請求,並調用store層服務對消息進行處理。HA服務的基本單元,支持同步雙寫,異步雙寫等模式。rocketmq-clien
::mq客戶端實現,目前官方僅僅開源了java版本的mq客戶端,c++,go客戶端有社區開源貢獻。rocketmq-common
:一些模塊間通用的功能類,比如一些配置文件、常量。rocketmq-example
:官方提供的例子,對典型的功能比如order message,push consumer,pull consumer的用法進行了示範。rocketmq-filtersrv
:消息過濾服務,相當於在broker和consumer中間加入了一個filter代理。rocketmq-remoting
:基於netty的底層通信實現,所有服務間的交互都基於此模塊。rocketmq-srvut
:解析命令行的工具類。`rocketmq-store
:存儲層實現,同時包括了索引服務,高可用HA服務實現。rocketmq-tools
:mq集羣管理工具,提供了消息查詢等功能。
二:rocketmq-remoting通信層介紹
remoting模塊是mq的基礎通信模塊,理解通信層的原理對理解模塊間的交互很有幫助。底層基於Netty網絡庫驅動,因此需要先了解一些基本的Netty原理。
1、Netty基本知識
首先,我們需要了解Netty的線程模型:Netty運用了reactor模式,採用了監聽線程池和IO線程池分離的思想,數據的流轉在Netty中採取了類似職責鏈的設計模式,因此數據看起來就像在管道中流動一樣了。
現在我們只需要知道,我們能夠定義自己的handler並插入管道即可實現對數據的操作了。目前大致瞭解即可,稍後我們會結合mq的代碼講解。
2、mq通信協議
(1) 消息格式
重點關注一下header字段,他有2種編碼方式,一種JSON格式,另一種是ROCKETMQ格式。重點關注JSON格式:
這裏直接引用了官方文檔裏的圖片,RequestCode.java
和ResponseCode.java
文件包含了所有的操作碼,推薦調試2個模塊之間的通信的時候可以以操作碼爲索引。一個實際的請求如圖:
code=103表示他是一個REGISTER_BROKER
消息
(2) mq的消息處理邏輯
那麼,對於一個實際的請求,mq是如何進行編解碼以及分發請求的呢?比較重要的兩個類包括NettyRemotingClient和NettyRemotingServer,這裏以NettyRemotingServer爲例子先看它的啓動:
可以看到ch.pipeline().addLast就是往管道里添加數據的處理邏輯,首先需要知道對於每一個事件處理器handler,他可以處理的事件包括了以下幾種(覆蓋父類方法即可實現),只要滿足條件數據會經過每一個handler對應的事件處理方法:
channelActive
、channelInactive
:連接建立和連接關閉的時候會被回調。channelRead
:當channel有數據可讀時會回調到這個函數。mq正是從這個函數將請求分發到後端線程進行處理的。exceptionCaught
:發生異常時回調。userEventTriggered
:當上面的事件都不滿足自己的需求時,用戶可以在這裏面自定義的事件處理方法。
現在回頭來看,mq的pipeline管道定義如下handler的含義:NettyEncoder
、NettyDecoder
:mq對應的編碼器和解碼器的邏輯,他們分別覆蓋了父類的encode和decode方法。IdleStateHandler
:Netty自帶的心跳管理器NettyConnetManageHandle
:連接管理器,他負責捕獲新連接、連接斷開、異常等事件,然後統一調度到NettyEventExecuter處理器處理。NettyServerHandler
:當一個消息經過前面的解碼等步驟後,然後調度到channelRead0方法,然後根據消息類型進行分發
繼續跟蹤NettyServerHandler
代碼:
(a)處理消息請求processRequestCommand
首先看NettyRemotingAbstract
類中的一個成員:HashMap<Integer/* request code */, Pair<NettyRequestProcessor, ExecutorService>> processorTable
可以看到註釋,鍵表示了
request code
,mq中可以爲不同類型的請求碼指定不同的處理器Processor
處理,但是要注意消息實際的處理並不是在當前線程,而是被封裝成task放到Processor
對應的線程池處理:final RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd); pair.getObject2().submit(requestTask);
在RocketMQ中能看到很多地方都是這樣的處理,這樣的設計能夠最大程度的保證異步,保證每個線程都專注處理自己負責的東西。 以下是
Processor
的實現:
最後,processRequestCommand
這個函數的整體處理邏輯如下所示:
另外,要注意一下,第二步構建task的時候,運用了模板設計模式,在任務的執行前後加入了一個hook:我們可以利用這個hook進行一些額外的操作,比如消息的加密解密。
rpcHook.doBeforeRequest(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd);
Processor.processRequest()
rpcHook.doAfterResponse(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd, response);
(b)處理消息響應processResponseCommand
實際上,這部分的處理並不是太難,首先理解下面這個結構:
protected final ConcurrentHashMap<Integer /* opaque */, ResponseFuture> responseTable
opaque表示請求發起連接方在同個連接上不同的請求標識代碼,每次發送一個消息的時候,他可以選擇同步阻塞的方式和異步非阻塞的方式,不管是哪種方式,他都會保存操作碼到ResponseFuture的映射。
重點講解一下ResponseFuture
這個類,這個類中比較重要的成員包括一個回調函數invokeCallback
,以及一個信號量semaphore
。
- 對於同步消息,這二個參數通常是個null。
- 對於異步消息,
invokeCallback
的作用就是在收到消息響應的時候能夠根據responseTable
找到操作碼對應的回調函數;semaphore
的主要作用是用作流控,當多個線程同時往一個連接寫數據時可以通過信號量控制permit同時寫許可的數量。
簡單來說,總體流程如下:
當然,流程圖未列舉的操作還包括釋放信號量資源,以及清空responseTable
表相關鍵值對信息等操作。
還需要知道的是,爲了及時的處理responseTable
內的信息,有一個定時回查線程會定期回查responseTable
,處理方法與上面類似,參考scanResponseTable
代碼。
·NettyRemotingClient·的處理實際上與NettyRemotingServer
的處理基本一致,唯一不同的是Netty pipeline中連接管理相關的handler額外還處理了connect事件
,該事件在客戶端主動連接對端成功後回調。
原文地址:http://blog.csdn.net/a2888409/article/details/53838580
另:processMessageReceived方法是外部類的方法,內部類中可以直接用方法名調用外部類的方法