1. 程式人生 > >dubbo原始碼分析-遠端暴露

dubbo原始碼分析-遠端暴露

chapter III

什麼是服務暴露

  服務暴露,通常也叫作服務釋出,通過語義可以理解為將一個服務“暴露” 出去,本質上我覺得可以簡單的認為只是組裝一個服務域物件,儲存起來,然後做些“暴露”相關的工作,方便客戶端以某種契約(協議)進行rpc呼叫。

為什麼要進行遠端暴露

  關於為什麼要進行遠端暴露,我們可以從兩個角度去分析:一、遠端暴露和本地暴露有什麼區別和聯絡;二、遠端暴露涉及到哪些內容。把這兩個問題解釋清楚也就等於回答了為什麼要進行遠端暴露這個問題。   首先,遠端暴露和本地暴露的區別本質上本地暴露將轉換的exporter裝入InjvmProtocol的exporterMap中,而遠端暴露是將exporter裝入了協議所屬Protocol的exporterMap中,比如使用dubbo協議進行服務暴露的話,那麼exporter會被裝入DubboProtocol,具體為什麼我們會在引用服務章節去分析

;然後我理解的本地暴露設計的初衷可能是為了規定服務提供方和服務呼叫方在同一應用內的呼叫方式,即如果沒有本地暴露,那麼不管通訊雙方是否在同一應用都需要走socket進行資料的交換,換句話說,如果進行了本地暴露,那麼同一應用的雙方可以直接在同一jvm中完成服務呼叫,減少了沒必要的網路消耗;

遠端暴露涉及哪些內容

  遠端暴露涉及到的內容包括:

  • 將服務暴露到指定的協議維護的exporterMap集合中
  • 開啟傳輸層服務(netty、mina等rpc框架),注意同一個埠只開啟一次,在第一次服務暴露的時候開啟,隨後服務暴露會觸發服務重啟,而預設情況下,服務重啟只是將當前服務url的引數追加到原本儲存在AbstractPeer中的url。
  • 連線註冊中心,註冊服務,並且監聽狀態變更
  • 訂閱服務(因為每個服務提供者也可能是服務的消費者) 而本地暴露只是將exporter裝載到InjvmProtocol的porterMap集合中。 相信上訴通過對本地暴露和遠端暴露的區別以及遠端暴露包含的具體內容的討論,可以解釋dubbo為什麼要遠端暴露了(回答問題的角度是多種多樣的,只要能把事情說清楚說透徹就可以)。

程式碼流程分析

遠端暴露入口

  • RegistryProtocol#export()函式,如圖: 在這裡插入圖片描述

具體暴露邏輯

因為我們沒有配置scope,所以預設全部暴露,暴露遠端服務的時候會做以下動作: (1).會遍歷註冊中心進行暴露(上文說過dubbo支援多註冊中心服務暴露); (2).將配置的監控monitor註冊到URL上,(這裡簡單提一下 :dubbo預設會在Invoker中構造一個MonitorFilter,這個filter中持有一份MonitorFactory,這個MonitorFactory是也是基於spi擴充套件載入到成員變數中的,預設為DubboMonitorFactory,這個factory會生產DubboMonitor,最終在Invoker呼叫到MonitorFilter的invoke方法時,會根據過濾配置了monitor的請求URL,然後進行監控資料採集,後面有時間的話也會展開分析這塊,並且可以嘗試做一個自定義的Monitor

),回到主線任務; (3)接下來就是獲取服務的可執行物件Invoker,得到Invoker過程和本地暴露一致,不在贅述; (4)接下來會封裝一個代理Invoker物件; (5)然後進行遠端暴露的Invoker轉Exporter。

  • 遠端暴露過程中,在Protocol為RegistryProtocol例項,ProtocolListenerWrapper,ProtocolFilterWrapper不做處理,直接走到RegistryProtocol#export()方法,如圖: 在這裡插入圖片描述 直接進入RegistryProtocol#export(): 在這裡插入圖片描述

  • RegistryProtocol#export()邏輯分析: PS:本章只分析doLocalExport(),生成具體遠端暴露協議的Exporter以及開啟通訊層。 (1)首先執行doLocalExport(originInvoker)函式,如圖: 在這裡插入圖片描述 這個函式執行流程 I.先從快取中獲取exporter,如果沒有的話,就進行建立。 II.使用代理invoker包裝invoker III.執行protocol#exprot(),根據下圖可以判斷此刻的protocol最內層將是dubboProtocol,外層會被wrapper類包裹,由於上篇已經講過這裡就不贅述,我們直接跟到DubboProtocol的export方法。 在這裡插入圖片描述 (2) DubboProtocol#export做了三件事如圖: 在這裡插入圖片描述

    I. 建立DubboExporter物件並放入dubbo協議的exporterMap中(即DubboExporter的exporterMap)。 II. 判斷是否為客戶端存根 III. 開啟netty服務,如圖 在這裡插入圖片描述 開啟netty服務核心程式碼是DubboProtocol#createServer(url)如圖(接下來是一片連續的程式碼截圖):在這裡插入圖片描述 圖2.1 在這裡插入圖片描述 圖2.2在這裡插入圖片描述 圖2.3 在這裡插入圖片描述 圖2.4在這裡插入圖片描述 圖2.5 在這裡插入圖片描述 圖2.6

    大概的說一下上訴程式碼截圖的邏輯:

  • 首先會設定heartbeat(圖2.1),

  • 進行引數驗證,判斷指定的傳輸層server是否存在

  • 指定編解碼,預設DubboCodec

  • 執行Exchangers#bind函式(圖2.2) 1. 校驗url和handler引數 2. Exchangers#getExchanger(URL),從url中獲取引數exchanger,預設為header(圖2.3), 3. Exchangers#getExchanger(String),通過spi獲取HeaderExchanger(圖2.4) 4. 此時可以看到得到的交換層具體實現為HeaderExchanger,執行HeaderExchanger# bind函式(圖2.5) 5. HeaderExchanger# bind內部執行邏輯可以用一個圖來解釋(圖2.6&圖2.7):

   return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
                    |                          |                     |                    |                  |
                    V                          |                     |                    |                  V
            1.提供統一的服務操作介面              |                     |                    |                利用裝飾器模式,這個才是最靠近業務的邏輯(直接呼叫相關的invoker)
            2.建立心跳定時任務                  V                     |                    |
                                    1.利於擴充套件點機制選擇通訊框架     |                    |
                                    2.格式化回撥函式                 |                    |
                                                                 V                    V
                                                            訊息的解碼???         處理dubbo的通訊模型:單向,雙向,非同步等通訊模型

圖2.7來源blog地址 再簡單說下我個人理解HeaderExchangeHandler,這個是處理具體請求的Handler,DecodeHandler是負責編解碼的Handler,然後通過裝飾設計模式在DecodeHandler內部維護一個HeaderExchangeHandler,這樣的作用其實很明顯,就是在呼叫具體請求handler的時候,必須經過編解碼handler(實際上我們從對端拿到資料是必須要經過解碼的);然後Transporters.bind()的作用體現在兩個方面:一、在真正的呼叫具體Transporter之前做一些處理,比如說,它內部對引數進行了校驗;二、遮蔽掉具體的Transporter(傳輸層實現),起到一定的適配作用;最後在完成埠繫結之後返回一個HeaderExchangeServer,這個HeaderExchangeServer可以作為統一呼叫的入口。

  1. 最終真正執行繫結的是NettyTransporter,如圖: 在這裡插入圖片描述 NettyTransporter通過構造NettyServer完成埠的繫結監聽,也就是說在構建NettyServer物件完成時就已經實現了埠的繫結。NettyServer類圖如下: 在這裡插入圖片描述 首先進入NettyServer建構函式如圖: 這裡我們可以看到首先會呼叫ChannelHandlers#wrap函式,這一步的作用可以理解為是在構建ChannelHandler呼叫鏈,構建後的ChannelHandler如圖:在這裡插入圖片描述然後將url和ChannelHandler鏈傳給AbstractServer(根據 NettyServer類圖可以判斷),委託父類建構函式完成其餘功能,如圖:在這裡插入圖片描述 AbstractServer只是做了一些抽象通用的初始化工作,比如設定編解碼器,以及連線屬性(timeout…);關鍵是doOpen(),doOpen()是個模板方法,也是繫結埠的關鍵,具體可以看NettyServer#doOpen,如圖:在這裡插入圖片描述 dubbo 2.6.5 封裝的Netty版本是3.X,jboss netty和netty4的用法很類似,我們貼圖對比一下:在這裡插入圖片描述 流程大概是一致的,無非就是構造Boos和Worker執行緒池,指定ChannelFactory用來生產Channel;構造輔助啟動類ServerBootstrap,設定ChannelPipeline,ChannelPipeline中新增新增ChannelHandler(編解碼handler和業務處理handler),呼叫輔助啟動類的bind方法進行埠繫結。 這裡有個細節需要關注一下:
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();

  這個NettyHandler是dubbo自己封裝的一個handler,繼承自netty的SimpleChannelHandler,可以看出它的職能是處理netty中觸發的各種事件,類似於netty4的ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter功能的聚合,內部需要維護一個ChannelHandler(dubbo內部提供,通過擴充套件點機制可以載入具體實現),這個ChannelHandler是事件處理的核心,所有netty的事件都將被委託給ChannelHandler,具體的處理流程採用責任鏈模式(比如檢查是否是心跳訊息等),可以理解它被用於具體的處理各種事件,而ChannelHandler在處理具體事件的時候是將netty的Channel遮蔽了的,統一用NettyChannel去處理,NettyChannel內部會維護Netty原生的channel,由此可知,dubbo完全遮蔽了netty的處理邏輯,實現了具體業務和通訊底層的剝離。   在構建這個handler的時候,會將NettyServer(NettyServer擁有ChannelHandler的特性)維護進去。然後將NettyServer的channels指向NettyHandler的channels,方便獲取;

文末

總結

  上訴簡單的說了遠端服務暴露的兩個步驟,即:生成Exporter,開啟傳輸服務(netty)。距離完整暴露完成還差一個針對註冊中心的相關操作。

還望賜教

  鑑於本人才疏學淺,談到的內容若有不對的地方煩請告知~也期望與大家一起交流共同進步。