Dubbo——服务暴露的实现原理
配置承載初始化
不管在服務暴露還是服務消費場景下,Dubbo框架都會根據優先級對配置信息做聚合處理,目前默認覆蓋策略主要遵循以下幾點規則:
一般推薦使用dubbo.properties作為默認值,只有XML沒有配置時,dubbo.properties配置項才會生效,通常用于共享配置,比如應用名等。
Dubbo的配置也會受到provider的影響,這個屬于運行期屬性值影響,同樣遵循以下幾點規則:
運行時屬性隨著框架特性可以動態添加,因此覆蓋策略中包含的屬性沒辦法全部列出來,一般不允許透傳的屬性都會在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進行更細粒度的控制,比如先進行服務暴露再注冊服務元數據。注冊中心在做服務暴露時依次做了一下幾件事:
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:
總結
以上是生活随笔為你收集整理的Dubbo——服务暴露的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go语言:HTTP客户端请求设置用户浏览
- 下一篇: 天翼物联亮相2022中国信息通信业发展高