Dubbo——服務暴露的實現原理

配置承載初始化

不管在服務暴露還是服務消費場景下,Dubbo框架都會根據優先級對配置信息做聚合處理,目前默認覆蓋策略主要遵循以下幾點規則:

  1. -D 傳遞給JVM參數優先級最高,比如-Ddubbo.protocol.port=20880。
  2. 代碼或XML配置優先級次高,比如Spring中XML文件制定<dubbo:protocol port="20880"/>
  3. 配置文件優先級最低,比如dubbo.properties文件制定dubbo.protocol.port=20880。

一般推薦使用dubbo.properties作爲默認值,只有XML沒有配置時,dubbo.properties配置項纔會生效,通常用於共享配置,比如應用名等。

Dubbo的配置也會受到provider的影響,這個屬於運行期屬性值影響,同樣遵循以下幾點規則:

  1. 如果只有provider端指定配置,則會自動透傳到客戶端(比如timeout)。
  2. 如果客戶端也配置了響應屬性,則服務端配置會被覆蓋(比如timeout)。

運行時屬性隨着框架特性可以動態添加,因此覆蓋策略中包含的屬性沒辦法全部列出來,一般不允許透傳的屬性都會在ClusterUtils#mergeUrl中進行處理。

遠程服務的暴露機制

整體RPC的暴露原理:

	ServiceConfig ------> ref
		 ↓
 	ProxyFactory  --------> Javassist、JDK動態代理
 	     ↓
 	  Invoker  ----------> AbstractProxyInvoker
 	 	 ↓
 	  Protocol --------> Dubbo、injvm等
 	  	 ↓
 	 Exporter

在整體上看,Dubbo框架做服務暴露分爲兩大部分,第一步將持有的服務實例通過代理轉換成Invoker,第二步會把Invoker通過具體協議(比如Dubbo)轉換成Exporter,框架做了這層抽象大大方便了功能擴展。這裏的Invoker可以簡單理解成一個真實的服務對象實例,是Dubbo框架實體域,所有模型都會向它靠攏,可向它發起invoer調用。它可能是本地的實現,也可能是一個遠程的實現,還可能是一個集羣的實現。

框架真正進行服務暴露的入口點在ServiceConfig#doExport中,無論XML還是註解,都會轉換成ServiceBean,它集成自ServiceConfig,在服務暴露之前會按照上面的覆蓋策略生效,主要處理思路就是遍歷服務的所有方法,如果沒有值則嘗試從 -D 選項中讀取,如果還沒有則自動從配置文件dubbo.properties中讀取。

Dubbo支持多註冊中心同時寫,如果配置了服務同時註冊多個註冊中心,則會在ServiceConfig#doExportUrls中依次暴露:
在這裏插入圖片描述
Dubbo也支持相同服務暴露多個協議,比如同時暴露Dubbo和REST協議,框架內部會依次對使用的協議都依次服務暴露,每個協議註冊元數據都會寫入多個註冊中心。在①中會自動獲取用戶配置的註冊中心,如果沒有顯示指定註冊中心,則默認會用全局配置的註冊中心。在②處理多協議服務暴露的場景,真實服務暴露邏輯是在doExportUrlsFor1Protocol方法中實現的:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

  • 在①中主要通過反射獲取配置對象並放到map中用於後續構造URL參數(比如應用名等)
  • 在②中主要區分全局配置,默認在屬性前面增加default.前綴,當框架獲取URL中的參數時,如果不存在則會自動嘗試獲取default. 前綴對應的值
  • 在③中主要處理本地內存JVM協議暴露
  • 在④中主要追加監控上報地址,框架會在攔截器中執行數據上報,這部分是可選的
  • 在⑤中會通過動態代理的方式創建Invoker對象,在服務端生成的是AbstractProxyInvoker實例,所有真實的方法調用都會委託給代理,然後代理轉發給服務ref調用。目前框架實現兩種代理:JavassistProxyFactory和JdkProxyFactory
    • JavassistProxyFactory模式原理:創建Wrapper子類,在子類中實現invokeMethod方法,方法體內會爲每個ref方法都做方法名和方法參數匹配校驗,如果匹配則直接調用即可,相比JDKProxyFactory省去了反射調用的開銷。
    • JDKProxyFactory:通過反射獲取真實對象的方法,然後調用即可。
  • 在⑥中主要先觸發服務暴露(端口打開等),然後進行服務元數據註冊
  • 在⑦中主要處理沒有使用註冊中心的場景,直接進行服務暴露不需要元數據註冊,因爲這裏暴露的URL信息是以具體RPC協議開頭的,並不是以註冊中心協議開頭的。

爲了更容易地理解服務暴露於註冊中心的乾洗,以下列表分別展示有註冊中心和無註冊中心的URL:

  • registry://host:port/com.alibaba.dubbo.registry.RegistryService?protocol==zookeeper&export=dubbo://ip:port/xxx?..
  • dubbo://ip:host/xxx.Service?timeout=1000&..

protocol實例會自動根據服務暴露URL自動做適配,有註冊中心場景會取出具體協議,比如Zookeeper,首先會創建註冊中心實例,然後取出export對應的具體服務URL,最後用服務URL對應的協議(默認爲Dubbo)進行服務暴露,當服務暴露成功後把服務數據註冊到ZooKeeper。如果沒有註冊中心,則在⑦中會自動判斷URL對應的協議(Dubbo)並直接暴露服務,從而沒有經過註冊中心。

將服務實例ref轉換成Invoker之後,如果有註冊中心時,則會通過RegistryProtocol#export進行更細粒度的控制,比如先進行服務暴露再註冊服務元數據。註冊中心在做服務暴露時依次做了一下幾件事:

  1. 委託具體協議(Dubbo)進行服務暴露,創建NettyServer監聽端口和保存服務實例。
  2. 創建註冊中心對象,與註冊中心創建TCP連接。
  3. 註冊服務元數據到註冊中心。
  4. 訂閱configuators節點,監聽服務動態屬性變更事件。
  5. 服務銷燬收尾工作,比如關閉端口、反註冊服務信息等。

在這裏插入圖片描述
DestroyableExporter中重寫的unexport()方法如下:
在這裏插入圖片描述

當服務真實調用時會觸發各種攔截器Filter,這個是在哪裏初始化的呢?在①中進行服務暴露前,框架會做攔截器初始化,Dubbo在加載protocol擴展點時會自動注入ProtocolListennerWrapper和ProtocolFilterWrapper

ProtocolFilterWrapper --> ProtocolListennerWrapper --> DubboProtocol

在ProtocolListenerWrapper實現中,在對服務提供者進行暴露時回調對應的監聽器方法。ProtocolFilterWrapper會調用下一級ListenerExporterWrapper#export方法,在該方法內部會觸發buildInvokerChain進行攔截器構造:
在這裏插入圖片描述
在這裏插入圖片描述

①:在觸發Dubbo協議暴露前先對服務Invoker做了一層攔截器構建,在加載所有攔截器時會過濾只對provider生效的數據。

②: 首先獲取真實服務ref對應的Invoker並掛載到整個攔截器鏈尾部,然後逐級包裹其他攔截器,這樣保證了真實服務調用是最後觸發的。

③:逐層轉發攔截器服務調用,是否調用下一個攔截器由具體攔截器實現。

在構造調用攔截器之後會調用Dubbo協議進行服務暴露:
在這裏插入圖片描述
在這裏插入圖片描述
①和②:中主要根據服務分組、版本、服務接口和暴露端口作爲key用於關聯具體服務Invoker。
③:對服務暴露做校驗判斷,因爲同一個協議暴露有很多接口,只有初次暴露的接口才需要打開端口監聽。
然後在④中觸發HeaderExchanger中綁定的方法,最後會調用底層NettyServer進行處理。在初始化Server過程中會初始化很多Handl用於支持一些特性,比如心跳、業務線程池處理編解碼的Handler和響應方法調用的Handler。

本地服務的暴露機制

很多實用Dubbo框架的應用可能存在同一個JVM暴露了遠程服務,同時同一個JVM內部又引用了自身服務的情況,Dubbo默認會把遠程服務用injvm協議再暴露一份,這樣消費方直接消費同一個JVM內部的服務,避免了跨網絡進行遠程通信。

在這裏插入圖片描述

通過exportLocal實現可以發現,在①中顯式Dubbo指定用injvm協議暴露服務,這個協議比較特殊,不會做端口打開操作,僅僅把服務保存在內存中而已。在②中會提取URL中的協議,在InjvmProtocol類中存儲服務實例信息,它的實現也是非常直接了當的,直接返回InjvmExporter實例對象,構造函數內部會把當前Invoker加入exporterMap:
在這裏插入圖片描述
在這裏插入圖片描述

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