溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Dubbo的服務(wù)引用過(guò)程是什么

發(fā)布時(shí)間:2021-10-28 15:48:23 來(lái)源:億速云 閱讀:108 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要講解了“Dubbo的服務(wù)引用過(guò)程是什么”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Dubbo的服務(wù)引用過(guò)程是什么”吧!

服務(wù)引用大致流程

我們已經(jīng)得知 Provider將自己的服務(wù)暴露出來(lái),注冊(cè)到注冊(cè)中心,而 Consumer無(wú)非就是通過(guò)一波操作從注冊(cè)中心得知  Provider 的信息,然后自己封裝一個(gè)調(diào)用類和 Provider 進(jìn)行深入地交流。

而之前的文章我都已經(jīng)提到在 Dubbo中一個(gè)可執(zhí)行體就是 Invoker,所有調(diào)用都要向 Invoker 靠攏,因此可以推斷出應(yīng)該要先生成一個(gè)  Invoker,然后又因?yàn)榭蚣苄枰磺秩霕I(yè)務(wù)代碼的方向發(fā)展,那我們的 Consumer  需要無(wú)感知的調(diào)用遠(yuǎn)程接口,因此需要搞個(gè)代理類,包裝一下屏蔽底層的細(xì)節(jié)。

整體大致流程如下:

Dubbo的服務(wù)引用過(guò)程是什么

服務(wù)引入的時(shí)機(jī)服務(wù)的引入和服務(wù)的暴露一樣,也是通過(guò) spring 自定義標(biāo)簽機(jī)制解析生成對(duì)應(yīng)的 Bean,Provider Service 對(duì)應(yīng)解析的是  ServiceBean 而 Consumer Reference 對(duì)應(yīng)的是 ReferenceBean。

Dubbo的服務(wù)引用過(guò)程是什么

前面服務(wù)暴露的時(shí)機(jī)我們上篇文章分析過(guò)了,在 Spring 容器刷新完成之后開(kāi)始暴露,而服務(wù)的引入時(shí)機(jī)有兩種,第一種是餓漢式,第二種是懶漢式。

餓漢式是通過(guò)實(shí)現(xiàn) Spring 的InitializingBean接口中的 afterPropertiesSet方法,容器通過(guò)調(diào)用  ReferenceBean的 afterPropertiesSet方法時(shí)引入服務(wù)。

懶漢式是只有當(dāng)這個(gè)服務(wù)被注入到其他類中時(shí)啟動(dòng)引入流程,也就是說(shuō)用到了才會(huì)開(kāi)始服務(wù)引入。

默認(rèn)情況下,Dubbo 使用懶漢式引入服務(wù),如果需要使用餓漢式,可通過(guò)配置 dubbo:reference 的 init 屬性開(kāi)啟。

我們可以看到 ReferenceBean還實(shí)現(xiàn)了FactoryBean接口,這里有個(gè)關(guān)于 Spring 的面試點(diǎn)我?guī)Т蠹曳治鲆徊ā?/p>

BeanFactory  、FactoryBean、ObjectFactory

就是這三個(gè)玩意,我單獨(dú)拿出來(lái)說(shuō)一下,從字面上來(lái)看其實(shí)可以得知BeanFactory、ObjectFactory是個(gè)工廠而FactoryBean是個(gè)  Bean。

BeanFactory 其實(shí)就是 IOC 容器,有多種實(shí)現(xiàn)類我就不分析了,簡(jiǎn)單的說(shuō)就是 Spring 里面的 Bean  都?xì)w它管,而FactoryBean也是 Bean 所以說(shuō)也是歸 BeanFactory 管理的。

那 FactoryBean 到底是個(gè)什么 Bean 呢?它其實(shí)就是把你真實(shí)想要的 Bean 封裝了一層,在真正要獲取這個(gè) Bean 的時(shí)候容器會(huì)調(diào)用  FactoryBean#getObject() 方法,而在這個(gè)方法里面你可以進(jìn)行一些復(fù)雜的組裝操作。

這個(gè)方法就封裝了真實(shí)想要的對(duì)象復(fù)雜的創(chuàng)建過(guò)程。

到這里其實(shí)就很清楚了,就是在真實(shí)想要的 Bean 創(chuàng)建比較復(fù)雜的情況下,或者是一些第三方 Bean 難以修改的情形,使用 FactoryBean  封裝了一層,屏蔽了底層創(chuàng)建的細(xì)節(jié),便于 Bean 的使用。

而 ObjectFactory 這個(gè)是用于延遲查找的場(chǎng)景,它就是一個(gè)普通工廠,當(dāng)?shù)玫?ObjectFactory 對(duì)象時(shí),相當(dāng)于 Bean  沒(méi)有被創(chuàng)建,只有當(dāng) getObject() 方法時(shí),才會(huì)觸發(fā) Bean 實(shí)例化等生命周期。

主要用于暫時(shí)性地獲取某個(gè) Bean Holder 對(duì)象,如果過(guò)早的加載,可能會(huì)引起一些意外的情況,比如當(dāng) Bean A 依賴 Bean B  時(shí),如果過(guò)早地初始化 A,那么 B 里面的狀態(tài)可能是中間狀態(tài),這時(shí)候使用 A 容易導(dǎo)致一些錯(cuò)誤。

總結(jié)的說(shuō) BeanFactory 就是 IOC 容器,F(xiàn)actoryBean 是特殊的 Bean, 用來(lái)封裝創(chuàng)建比較復(fù)雜的對(duì)象,而  ObjectFactory 主要用于延遲查找的場(chǎng)景,延遲實(shí)例化對(duì)象。

服務(wù)引入的三種方式

服務(wù)的引入又分為了三種,第一種是本地引入、第二種是直接連接引入遠(yuǎn)程服務(wù)、第三種是通過(guò)注冊(cè)中心引入遠(yuǎn)程服務(wù)。

Dubbo的服務(wù)引用過(guò)程是什么

本地引入不知道大家是否還有印象,之前服務(wù)暴露的流程每個(gè)服務(wù)都會(huì)通過(guò)搞一個(gè)本地暴露,走 injvm 協(xié)議(當(dāng)然你要是 scope = remote  就沒(méi)本地引用了),因?yàn)榇嬖谝粋€(gè)服務(wù)端既是 Provider 又是 Consumer  的情況,然后有可能自己會(huì)調(diào)用自己的服務(wù),因此就弄了一個(gè)本地引入,這樣就避免了遠(yuǎn)程網(wǎng)絡(luò)調(diào)用的開(kāi)銷。

所以服務(wù)引入會(huì)先去本地緩存找找看有沒(méi)有本地服務(wù)。

直連遠(yuǎn)程引入服務(wù),這個(gè)其實(shí)就是平日測(cè)試的情況下用用,不需要啟動(dòng)注冊(cè)中心,由 Consumer 直接配置寫死 Provider  的地址,然后直連即可。

注冊(cè)中心引入遠(yuǎn)程服務(wù),這個(gè)就是重點(diǎn)了,Consumer 通過(guò)注冊(cè)中心得知 Provider  的相關(guān)信息,然后進(jìn)行服務(wù)的引入,這里還包括多注冊(cè)中心,同一個(gè)服務(wù)多個(gè)提供者的情況,如何抉擇如何封裝,如何進(jìn)行負(fù)載均衡、容錯(cuò)并且讓使用者無(wú)感知,這就是個(gè)技術(shù)活。

本文用的就是單注冊(cè)中心引入遠(yuǎn)程服務(wù),讓我們來(lái)看看 Dubbo 是如何做的吧。

服務(wù)引入流程解析

默認(rèn)是懶漢式的,所以服務(wù)引入的入口就是 ReferenceBean 的 getObject 方法。

Dubbo的服務(wù)引用過(guò)程是什么

可以看到很簡(jiǎn)單,就是調(diào)用 get 方法,如果當(dāng)前還沒(méi)有這個(gè)引用那么就執(zhí)行 init 方法。

官網(wǎng)的一個(gè)小問(wèn)題這個(gè)問(wèn)題

就在 if (ref == null) 這一行,其實(shí)是一位老哥在調(diào)試的時(shí)候發(fā)現(xiàn)這個(gè) ref 竟然不等于 null,因此就進(jìn)不到  init 方法里面調(diào)試了,后來(lái)他發(fā)現(xiàn)是因?yàn)?IDEA 為了顯示對(duì)象的信息,會(huì)通過(guò) toString 方法獲取對(duì)象對(duì)應(yīng)的信息。

toString 調(diào)用的是 AbstractConfig#toString,而這個(gè)方法會(huì)通過(guò)反射調(diào)用了 ReferenceBean 的 getObject  方法,觸發(fā)了引入服務(wù)動(dòng)作,所以說(shuō)到斷點(diǎn)的時(shí)候 ref != null。

Dubbo的服務(wù)引用過(guò)程是什么

可以看到是通過(guò)方法名來(lái)進(jìn)行反射調(diào)用的,而 getObject 就是 get 開(kāi)頭的,因此會(huì)被調(diào)用。

所以這個(gè)哥們提了個(gè) PR,但是一開(kāi)始沒(méi)有被接受,一位 Member 認(rèn)為這不是 bug, idea 設(shè)置一下不讓調(diào)用 toString 就好了。

Dubbo的服務(wù)引用過(guò)程是什么

不過(guò)另一位 Member 覺(jué)得這個(gè) PR 挺好的,并且 Dubbo 項(xiàng)目二代掌門人北緯30也發(fā)話了,因此這個(gè) PR 被受理了。


Dubbo的服務(wù)引用過(guò)程是什么

至此我們已經(jīng)知道這個(gè)小問(wèn)題了,然后官網(wǎng)上其實(shí)也寫的很清楚。

Dubbo的服務(wù)引用過(guò)程是什么

但是小問(wèn)題來(lái)了,之前我在文章提到我的源碼版本是 2.6.5,是在 github 的 releases 里面下的,這個(gè) tostring  問(wèn)題其實(shí)我挺早之前就知道了,我想的是我 2.6.5 穩(wěn)的一批,誰(shuí)知道翻車了。

Dubbo的服務(wù)引用過(guò)程是什么

我調(diào)試的時(shí)候也沒(méi)進(jìn)到 init 方法因?yàn)?ref 也沒(méi)等于 null,我就奇怪了,我里面去看了下 toString  方法,2.6.5版本竟然沒(méi)有修改?沒(méi)有將 getObject 做過(guò)濾,因此還是被調(diào)用了。

我又打開(kāi)了2.7.5版本的代碼,發(fā)現(xiàn)是修改過(guò)的判斷。

Dubbo的服務(wù)引用過(guò)程是什么

我又去特意下了 2.6.6 版本的代碼,發(fā)現(xiàn)也是修改過(guò)的,因此這個(gè)修改并不是隨著 2.6.5版本發(fā)布,而是  2.6.6,除非我下的是個(gè)假包,這就是我說(shuō)的小問(wèn)題了,不過(guò)影響不大。

其實(shí)提到這一段主要想說(shuō)的是那個(gè)  PR,作為一個(gè)開(kāi)源軟件的輸出者,很多細(xì)節(jié)也是很重要的,這個(gè)問(wèn)題其實(shí)很影響源碼的調(diào)試,因?yàn)閷?duì)代碼不熟,肯定會(huì)一臉懵逼,誰(shuí)知道是不是哪個(gè)后臺(tái)線程異步引入了呢。

提這個(gè) PR 的老哥花了兩個(gè)小時(shí)才搞清楚真正的原因,所以說(shuō)雖然這不是個(gè) bug 但是很影響那些想深入了解 Dubbo  內(nèi)部結(jié)構(gòu)的同學(xué)們,這種改配置去適應(yīng)的方案是不可取了,還好最終的方案是改代碼。

好了讓我們回到今天的主題,接下來(lái)分析的就是那個(gè)不讓我進(jìn)去的 init 方法了。

源碼分析

init 方法很長(zhǎng),不過(guò)大部分就是檢查配置然后將配置構(gòu)建成 map ,這一大段我就不分析了,我們直接看一下構(gòu)建完的 map 長(zhǎng)什么樣。

Dubbo的服務(wù)引用過(guò)程是什么

然后就進(jìn)入重點(diǎn)方法 createProxy,從名字可以得到就是要?jiǎng)?chuàng)建的一個(gè)代理,因?yàn)榇a很長(zhǎng),我就一段一段的分析。

Dubbo的服務(wù)引用過(guò)程是什么

如果是走本地的話,那么直接構(gòu)建個(gè)走本地協(xié)議的 URL 然后進(jìn)行服務(wù)的引入,即  refprotocol.refer,這個(gè)方法之后會(huì)做分析,本地的引入就不深入了,就是去之前服務(wù)暴露的 exporterMap 拿到服務(wù)。

Dubbo的服務(wù)引用過(guò)程是什么

如果不是本地,那肯定是遠(yuǎn)程了,接下來(lái)就是判斷是點(diǎn)對(duì)點(diǎn)直連 provider 還是通過(guò)注冊(cè)中心拿到 provider 信息再連接 provider  了,我們分析一下配置了 url 的情況,如果配置了 url 那么不是直連的地址,就是注冊(cè)中心的地址。

Dubbo的服務(wù)引用過(guò)程是什么

然后就是沒(méi)配置 url 的情況,到這里肯定走的就是注冊(cè)中心引入遠(yuǎn)程服務(wù)了。

Dubbo的服務(wù)引用過(guò)程是什么

最終拼接出來(lái)的 URL 長(zhǎng)這樣。

Dubbo的服務(wù)引用過(guò)程是什么

可以看到這一部分其實(shí)就是根據(jù)各種參數(shù)來(lái)組裝 URL ,因?yàn)槲覀兊淖赃m應(yīng)擴(kuò)展都需要根據(jù) URL 的參數(shù)來(lái)進(jìn)行的。

Dubbo的服務(wù)引用過(guò)程是什么

至此我先畫個(gè)圖,給大家先捋一下。

Dubbo的服務(wù)引用過(guò)程是什么

這其實(shí)就是整個(gè)流程了,簡(jiǎn)述一下就是先檢查配置,通過(guò)配置構(gòu)建一個(gè) map ,然后利用 map 來(lái)構(gòu)建 URL ,再通過(guò) URL  上的協(xié)議利用自適應(yīng)擴(kuò)展機(jī)制調(diào)用對(duì)應(yīng)的 protocol.refer 得到相應(yīng)的 invoker 。

在有多個(gè) URL 的時(shí)候,先遍歷構(gòu)建出 invoker 然后再由 StaticDirectory 封裝一下,然后通過(guò) cluster  進(jìn)行合并,只暴露出一個(gè) invoker 。

然后再構(gòu)建代理,封裝 invoker 返回服務(wù)引用,之后 Comsumer 調(diào)用的就是這個(gè)代理類。

相信通過(guò)圖和上面總結(jié)性的簡(jiǎn)述已經(jīng)知道大致的服務(wù)引入流程了,不過(guò)還是有很多細(xì)節(jié),比如如何從注冊(cè)中心得到 Provider 的地址,invoker  里面到底是怎么樣的?別急,我們繼續(xù)看。

從前面的截圖我們可以看到此時(shí)的協(xié)議是 registry 因此走的是 RegistryProtocol#refer,我們來(lái)看一下這個(gè)方法。

Dubbo的服務(wù)引用過(guò)程是什么

主要就是獲取注冊(cè)中心實(shí)例,然后調(diào)用 doRefer 進(jìn)行真正的 refer。

Dubbo的服務(wù)引用過(guò)程是什么

這個(gè)方法很關(guān)鍵,可以看到生成了RegistryDirectory 這個(gè) directory 塞了注冊(cè)中心實(shí)例,它自身也實(shí)現(xiàn)了NotifyListener  接口,因此注冊(cè)中心的監(jiān)聽(tīng)其實(shí)是靠這家伙來(lái)處理的。

然后向注冊(cè)中心注冊(cè)自身的信息,并且向注冊(cè)中心訂閱了 providers 節(jié)點(diǎn)、 configurators 節(jié)點(diǎn) 和 routers 節(jié)點(diǎn),訂閱了之后  RegistryDirectory 會(huì)收到這幾個(gè)節(jié)點(diǎn)下的信息,就會(huì)觸發(fā) DubboInvoker 的生成了,即用于遠(yuǎn)程調(diào)用的 Invoker。

然后通過(guò) cluster 再包裝一下得到 Invoker,因此一個(gè)服務(wù)可能有多個(gè)提供者,最終在 ProviderConsumerRegTable  中記錄這些信息,然后返回 Invoker。

所以我們知道Conusmer 是在 RegistryProtocol#refer 中向注冊(cè)中心注冊(cè)自己的信息,并且訂閱 Provider  和配置的一些相關(guān)信息,我們看看訂閱返回的信息是怎樣的。

Dubbo的服務(wù)引用過(guò)程是什么

拿到了Provider的信息之后就可以通過(guò)監(jiān)聽(tīng)觸發(fā) DubboProtocol# refer 了(具體調(diào)用哪個(gè) protocol 還是得看  URL的協(xié)議的,我們這里是 dubbo 協(xié)議),整個(gè)觸發(fā)流程我就不一一跟一下了,看下調(diào)用棧就清楚了。

Dubbo的服務(wù)引用過(guò)程是什么

終于我們從注冊(cè)中心拿到遠(yuǎn)程Provider 的信息了,然后進(jìn)行服務(wù)的引入。

Dubbo的服務(wù)引用過(guò)程是什么

這里的重點(diǎn)在 getClients,因?yàn)榻K究是要跟遠(yuǎn)程服務(wù)進(jìn)行網(wǎng)絡(luò)調(diào)用的,而 getClients 就是用于獲取客戶端實(shí)例,實(shí)例類型為  ExchangeClient,底層依賴 Netty 來(lái)進(jìn)行網(wǎng)絡(luò)通信,并且可以看到默認(rèn)是共享連接。

Dubbo的服務(wù)引用過(guò)程是什么

getSharedClient 我就不分析了,就是通過(guò)遠(yuǎn)程地址找 client ,這個(gè) client 還有引用計(jì)數(shù)的功能,如果該遠(yuǎn)程地址還沒(méi)有  client 則調(diào)用 initClient,我們就來(lái)看一下 initClient 方法。

Dubbo的服務(wù)引用過(guò)程是什么

而這個(gè)connect最終返回 HeaderExchangeClient里面封裝的是 NettyClient 。

Dubbo的服務(wù)引用過(guò)程是什么

然后最終得到的 Invoker就是這個(gè)樣子,可以看到記錄的很多信息,基本上該有的都有了,我這里走的是對(duì)應(yīng)的服務(wù)只有一個(gè) url 的情況,多個(gè) url  無(wú)非也是利用 directory和 cluster再封裝一層。

Dubbo的服務(wù)引用過(guò)程是什么

最終將調(diào)用 return (T) proxyFactory.getProxy(invoker); 返回一個(gè)代理對(duì)象,這個(gè)就不做分析了。

到這里,整個(gè)流程就是分析完了,不知道大家清晰了沒(méi)?我再補(bǔ)充前面的圖,來(lái)一個(gè)完整的流程給大家再過(guò)一遍。

Dubbo的服務(wù)引用過(guò)程是什么

感謝各位的閱讀,以上就是“Dubbo的服務(wù)引用過(guò)程是什么”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Dubbo的服務(wù)引用過(guò)程是什么這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI