Dubbo——服务暴露的实现原理
配置承載初始化
不管在服務(wù)暴露還是服務(wù)消費(fèi)場(chǎng)景下,Dubbo框架都會(huì)根據(jù)優(yōu)先級(jí)對(duì)配置信息做聚合處理,目前默認(rèn)覆蓋策略主要遵循以下幾點(diǎn)規(guī)則:
一般推薦使用dubbo.properties作為默認(rèn)值,只有XML沒有配置時(shí),dubbo.properties配置項(xiàng)才會(huì)生效,通常用于共享配置,比如應(yīng)用名等。
Dubbo的配置也會(huì)受到provider的影響,這個(gè)屬于運(yùn)行期屬性值影響,同樣遵循以下幾點(diǎn)規(guī)則:
運(yùn)行時(shí)屬性隨著框架特性可以動(dòng)態(tài)添加,因此覆蓋策略中包含的屬性沒辦法全部列出來,一般不允許透?jìng)鞯膶傩远紩?huì)在ClusterUtils#mergeUrl中進(jìn)行處理。
遠(yuǎn)程服務(wù)的暴露機(jī)制
整體RPC的暴露原理:
ServiceConfig ------> ref↓ProxyFactory --------> Javassist、JDK動(dòng)態(tài)代理↓Invoker ----------> AbstractProxyInvoker↓Protocol --------> Dubbo、injvm等↓Exporter在整體上看,Dubbo框架做服務(wù)暴露分為兩大部分,第一步將持有的服務(wù)實(shí)例通過代理轉(zhuǎn)換成Invoker,第二步會(huì)把Invoker通過具體協(xié)議(比如Dubbo)轉(zhuǎn)換成Exporter,框架做了這層抽象大大方便了功能擴(kuò)展。這里的Invoker可以簡(jiǎn)單理解成一個(gè)真實(shí)的服務(wù)對(duì)象實(shí)例,是Dubbo框架實(shí)體域,所有模型都會(huì)向它靠攏,可向它發(fā)起invoer調(diào)用。它可能是本地的實(shí)現(xiàn),也可能是一個(gè)遠(yuǎn)程的實(shí)現(xiàn),還可能是一個(gè)集群的實(shí)現(xiàn)。
框架真正進(jìn)行服務(wù)暴露的入口點(diǎn)在ServiceConfig#doExport中,無論XML還是注解,都會(huì)轉(zhuǎn)換成ServiceBean,它集成自ServiceConfig,在服務(wù)暴露之前會(huì)按照上面的覆蓋策略生效,主要處理思路就是遍歷服務(wù)的所有方法,如果沒有值則嘗試從 -D 選項(xiàng)中讀取,如果還沒有則自動(dòng)從配置文件dubbo.properties中讀取。
Dubbo支持多注冊(cè)中心同時(shí)寫,如果配置了服務(wù)同時(shí)注冊(cè)多個(gè)注冊(cè)中心,則會(huì)在ServiceConfig#doExportUrls中依次暴露:
Dubbo也支持相同服務(wù)暴露多個(gè)協(xié)議,比如同時(shí)暴露Dubbo和REST協(xié)議,框架內(nèi)部會(huì)依次對(duì)使用的協(xié)議都依次服務(wù)暴露,每個(gè)協(xié)議注冊(cè)元數(shù)據(jù)都會(huì)寫入多個(gè)注冊(cè)中心。在①中會(huì)自動(dòng)獲取用戶配置的注冊(cè)中心,如果沒有顯示指定注冊(cè)中心,則默認(rèn)會(huì)用全局配置的注冊(cè)中心。在②處理多協(xié)議服務(wù)暴露的場(chǎng)景,真實(shí)服務(wù)暴露邏輯是在doExportUrlsFor1Protocol方法中實(shí)現(xiàn)的:
- 在①中主要通過反射獲取配置對(duì)象并放到map中用于后續(xù)構(gòu)造URL參數(shù)(比如應(yīng)用名等)
- 在②中主要區(qū)分全局配置,默認(rèn)在屬性前面增加default.前綴,當(dāng)框架獲取URL中的參數(shù)時(shí),如果不存在則會(huì)自動(dòng)嘗試獲取default. 前綴對(duì)應(yīng)的值
- 在③中主要處理本地內(nèi)存JVM協(xié)議暴露
- 在④中主要追加監(jiān)控上報(bào)地址,框架會(huì)在攔截器中執(zhí)行數(shù)據(jù)上報(bào),這部分是可選的
- 在⑤中會(huì)通過動(dòng)態(tài)代理的方式創(chuàng)建Invoker對(duì)象,在服務(wù)端生成的是AbstractProxyInvoker實(shí)例,所有真實(shí)的方法調(diào)用都會(huì)委托給代理,然后代理轉(zhuǎn)發(fā)給服務(wù)ref調(diào)用。目前框架實(shí)現(xiàn)兩種代理:JavassistProxyFactory和JdkProxyFactory。
- JavassistProxyFactory模式原理:創(chuàng)建Wrapper子類,在子類中實(shí)現(xiàn)invokeMethod方法,方法體內(nèi)會(huì)為每個(gè)ref方法都做方法名和方法參數(shù)匹配校驗(yàn),如果匹配則直接調(diào)用即可,相比JDKProxyFactory省去了反射調(diào)用的開銷。
- JDKProxyFactory:通過反射獲取真實(shí)對(duì)象的方法,然后調(diào)用即可。
- 在⑥中主要先觸發(fā)服務(wù)暴露(端口打開等),然后進(jìn)行服務(wù)元數(shù)據(jù)注冊(cè)
- 在⑦中主要處理沒有使用注冊(cè)中心的場(chǎng)景,直接進(jìn)行服務(wù)暴露不需要元數(shù)據(jù)注冊(cè),因?yàn)檫@里暴露的URL信息是以具體RPC協(xié)議開頭的,并不是以注冊(cè)中心協(xié)議開頭的。
為了更容易地理解服務(wù)暴露于注冊(cè)中心的干洗,以下列表分別展示有注冊(cè)中心和無注冊(cè)中心的URL:
- registry://host:port/com.alibaba.dubbo.registry.RegistryService?protocol==zookeeper&export=dubbo://ip:port/xxx?..
- dubbo://ip:host/xxx.Service?timeout=1000&..
protocol實(shí)例會(huì)自動(dòng)根據(jù)服務(wù)暴露URL自動(dòng)做適配,有注冊(cè)中心場(chǎng)景會(huì)取出具體協(xié)議,比如Zookeeper,首先會(huì)創(chuàng)建注冊(cè)中心實(shí)例,然后取出export對(duì)應(yīng)的具體服務(wù)URL,最后用服務(wù)URL對(duì)應(yīng)的協(xié)議(默認(rèn)為Dubbo)進(jìn)行服務(wù)暴露,當(dāng)服務(wù)暴露成功后把服務(wù)數(shù)據(jù)注冊(cè)到ZooKeeper。如果沒有注冊(cè)中心,則在⑦中會(huì)自動(dòng)判斷URL對(duì)應(yīng)的協(xié)議(Dubbo)并直接暴露服務(wù),從而沒有經(jīng)過注冊(cè)中心。
將服務(wù)實(shí)例ref轉(zhuǎn)換成Invoker之后,如果有注冊(cè)中心時(shí),則會(huì)通過RegistryProtocol#export進(jìn)行更細(xì)粒度的控制,比如先進(jìn)行服務(wù)暴露再注冊(cè)服務(wù)元數(shù)據(jù)。注冊(cè)中心在做服務(wù)暴露時(shí)依次做了一下幾件事:
DestroyableExporter中重寫的unexport()方法如下:
當(dāng)服務(wù)真實(shí)調(diào)用時(shí)會(huì)觸發(fā)各種攔截器Filter,這個(gè)是在哪里初始化的呢?在①中進(jìn)行服務(wù)暴露前,框架會(huì)做攔截器初始化,Dubbo在加載protocol擴(kuò)展點(diǎn)時(shí)會(huì)自動(dòng)注入ProtocolListennerWrapper和ProtocolFilterWrapper。
ProtocolFilterWrapper --> ProtocolListennerWrapper --> DubboProtocol在ProtocolListenerWrapper實(shí)現(xiàn)中,在對(duì)服務(wù)提供者進(jìn)行暴露時(shí)回調(diào)對(duì)應(yīng)的監(jiān)聽器方法。ProtocolFilterWrapper會(huì)調(diào)用下一級(jí)ListenerExporterWrapper#export方法,在該方法內(nèi)部會(huì)觸發(fā)buildInvokerChain進(jìn)行攔截器構(gòu)造:
①:在觸發(fā)Dubbo協(xié)議暴露前先對(duì)服務(wù)Invoker做了一層攔截器構(gòu)建,在加載所有攔截器時(shí)會(huì)過濾只對(duì)provider生效的數(shù)據(jù)。
②: 首先獲取真實(shí)服務(wù)ref對(duì)應(yīng)的Invoker并掛載到整個(gè)攔截器鏈尾部,然后逐級(jí)包裹其他攔截器,這樣保證了真實(shí)服務(wù)調(diào)用是最后觸發(fā)的。
③:逐層轉(zhuǎn)發(fā)攔截器服務(wù)調(diào)用,是否調(diào)用下一個(gè)攔截器由具體攔截器實(shí)現(xiàn)。
在構(gòu)造調(diào)用攔截器之后會(huì)調(diào)用Dubbo協(xié)議進(jìn)行服務(wù)暴露:
①和②:中主要根據(jù)服務(wù)分組、版本、服務(wù)接口和暴露端口作為key用于關(guān)聯(lián)具體服務(wù)Invoker。
③:對(duì)服務(wù)暴露做校驗(yàn)判斷,因?yàn)橥粋€(gè)協(xié)議暴露有很多接口,只有初次暴露的接口才需要打開端口監(jiān)聽。
然后在④中觸發(fā)HeaderExchanger中綁定的方法,最后會(huì)調(diào)用底層NettyServer進(jìn)行處理。在初始化Server過程中會(huì)初始化很多Handl用于支持一些特性,比如心跳、業(yè)務(wù)線程池處理編解碼的Handler和響應(yīng)方法調(diào)用的Handler。
本地服務(wù)的暴露機(jī)制
很多實(shí)用Dubbo框架的應(yīng)用可能存在同一個(gè)JVM暴露了遠(yuǎn)程服務(wù),同時(shí)同一個(gè)JVM內(nèi)部又引用了自身服務(wù)的情況,Dubbo默認(rèn)會(huì)把遠(yuǎn)程服務(wù)用injvm協(xié)議再暴露一份,這樣消費(fèi)方直接消費(fèi)同一個(gè)JVM內(nèi)部的服務(wù),避免了跨網(wǎng)絡(luò)進(jìn)行遠(yuǎn)程通信。
通過exportLocal實(shí)現(xiàn)可以發(fā)現(xiàn),在①中顯式Dubbo指定用injvm協(xié)議暴露服務(wù),這個(gè)協(xié)議比較特殊,不會(huì)做端口打開操作,僅僅把服務(wù)保存在內(nèi)存中而已。在②中會(huì)提取URL中的協(xié)議,在InjvmProtocol類中存儲(chǔ)服務(wù)實(shí)例信息,它的實(shí)現(xiàn)也是非常直接了當(dāng)?shù)?#xff0c;直接返回InjvmExporter實(shí)例對(duì)象,構(gòu)造函數(shù)內(nèi)部會(huì)把當(dāng)前Invoker加入exporterMap:
總結(jié)
以上是生活随笔為你收集整理的Dubbo——服务暴露的实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go语言:HTTP客户端请求设置用户浏览
- 下一篇: 天翼物联亮相2022中国信息通信业发展高