您好,登錄后才能下訂單哦!
這篇文章主要講解了“Dubbo的服務引用過程是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Dubbo的服務引用過程是什么”吧!
服務引用大致流程
我們已經得知 Provider將自己的服務暴露出來,注冊到注冊中心,而 Consumer無非就是通過一波操作從注冊中心得知 Provider 的信息,然后自己封裝一個調用類和 Provider 進行深入地交流。
而之前的文章我都已經提到在 Dubbo中一個可執行體就是 Invoker,所有調用都要向 Invoker 靠攏,因此可以推斷出應該要先生成一個 Invoker,然后又因為框架需要往不侵入業務代碼的方向發展,那我們的 Consumer 需要無感知的調用遠程接口,因此需要搞個代理類,包裝一下屏蔽底層的細節。
整體大致流程如下:
服務引入的時機服務的引入和服務的暴露一樣,也是通過 spring 自定義標簽機制解析生成對應的 Bean,Provider Service 對應解析的是 ServiceBean 而 Consumer Reference 對應的是 ReferenceBean。
前面服務暴露的時機我們上篇文章分析過了,在 Spring 容器刷新完成之后開始暴露,而服務的引入時機有兩種,第一種是餓漢式,第二種是懶漢式。
餓漢式是通過實現 Spring 的InitializingBean接口中的 afterPropertiesSet方法,容器通過調用 ReferenceBean的 afterPropertiesSet方法時引入服務。
懶漢式是只有當這個服務被注入到其他類中時啟動引入流程,也就是說用到了才會開始服務引入。
默認情況下,Dubbo 使用懶漢式引入服務,如果需要使用餓漢式,可通過配置 dubbo:reference 的 init 屬性開啟。
我們可以看到 ReferenceBean還實現了FactoryBean接口,這里有個關于 Spring 的面試點我帶大家分析一波。
BeanFactory 、FactoryBean、ObjectFactory
就是這三個玩意,我單獨拿出來說一下,從字面上來看其實可以得知BeanFactory、ObjectFactory是個工廠而FactoryBean是個 Bean。
BeanFactory 其實就是 IOC 容器,有多種實現類我就不分析了,簡單的說就是 Spring 里面的 Bean 都歸它管,而FactoryBean也是 Bean 所以說也是歸 BeanFactory 管理的。
那 FactoryBean 到底是個什么 Bean 呢?它其實就是把你真實想要的 Bean 封裝了一層,在真正要獲取這個 Bean 的時候容器會調用 FactoryBean#getObject() 方法,而在這個方法里面你可以進行一些復雜的組裝操作。
這個方法就封裝了真實想要的對象復雜的創建過程。
到這里其實就很清楚了,就是在真實想要的 Bean 創建比較復雜的情況下,或者是一些第三方 Bean 難以修改的情形,使用 FactoryBean 封裝了一層,屏蔽了底層創建的細節,便于 Bean 的使用。
而 ObjectFactory 這個是用于延遲查找的場景,它就是一個普通工廠,當得到 ObjectFactory 對象時,相當于 Bean 沒有被創建,只有當 getObject() 方法時,才會觸發 Bean 實例化等生命周期。
主要用于暫時性地獲取某個 Bean Holder 對象,如果過早的加載,可能會引起一些意外的情況,比如當 Bean A 依賴 Bean B 時,如果過早地初始化 A,那么 B 里面的狀態可能是中間狀態,這時候使用 A 容易導致一些錯誤。
總結的說 BeanFactory 就是 IOC 容器,FactoryBean 是特殊的 Bean, 用來封裝創建比較復雜的對象,而 ObjectFactory 主要用于延遲查找的場景,延遲實例化對象。
服務引入的三種方式
服務的引入又分為了三種,第一種是本地引入、第二種是直接連接引入遠程服務、第三種是通過注冊中心引入遠程服務。
本地引入不知道大家是否還有印象,之前服務暴露的流程每個服務都會通過搞一個本地暴露,走 injvm 協議(當然你要是 scope = remote 就沒本地引用了),因為存在一個服務端既是 Provider 又是 Consumer 的情況,然后有可能自己會調用自己的服務,因此就弄了一個本地引入,這樣就避免了遠程網絡調用的開銷。
所以服務引入會先去本地緩存找找看有沒有本地服務。
直連遠程引入服務,這個其實就是平日測試的情況下用用,不需要啟動注冊中心,由 Consumer 直接配置寫死 Provider 的地址,然后直連即可。
注冊中心引入遠程服務,這個就是重點了,Consumer 通過注冊中心得知 Provider 的相關信息,然后進行服務的引入,這里還包括多注冊中心,同一個服務多個提供者的情況,如何抉擇如何封裝,如何進行負載均衡、容錯并且讓使用者無感知,這就是個技術活。
本文用的就是單注冊中心引入遠程服務,讓我們來看看 Dubbo 是如何做的吧。
服務引入流程解析
默認是懶漢式的,所以服務引入的入口就是 ReferenceBean 的 getObject 方法。
可以看到很簡單,就是調用 get 方法,如果當前還沒有這個引用那么就執行 init 方法。
官網的一個小問題這個問題
就在 if (ref == null) 這一行,其實是一位老哥在調試的時候發現這個 ref 竟然不等于 null,因此就進不到 init 方法里面調試了,后來他發現是因為 IDEA 為了顯示對象的信息,會通過 toString 方法獲取對象對應的信息。
toString 調用的是 AbstractConfig#toString,而這個方法會通過反射調用了 ReferenceBean 的 getObject 方法,觸發了引入服務動作,所以說到斷點的時候 ref != null。
可以看到是通過方法名來進行反射調用的,而 getObject 就是 get 開頭的,因此會被調用。
所以這個哥們提了個 PR,但是一開始沒有被接受,一位 Member 認為這不是 bug, idea 設置一下不讓調用 toString 就好了。
不過另一位 Member 覺得這個 PR 挺好的,并且 Dubbo 項目二代掌門人北緯30也發話了,因此這個 PR 被受理了。
至此我們已經知道這個小問題了,然后官網上其實也寫的很清楚。
但是小問題來了,之前我在文章提到我的源碼版本是 2.6.5,是在 github 的 releases 里面下的,這個 tostring 問題其實我挺早之前就知道了,我想的是我 2.6.5 穩的一批,誰知道翻車了。
我調試的時候也沒進到 init 方法因為 ref 也沒等于 null,我就奇怪了,我里面去看了下 toString 方法,2.6.5版本竟然沒有修改?沒有將 getObject 做過濾,因此還是被調用了。
我又打開了2.7.5版本的代碼,發現是修改過的判斷。
我又去特意下了 2.6.6 版本的代碼,發現也是修改過的,因此這個修改并不是隨著 2.6.5版本發布,而是 2.6.6,除非我下的是個假包,這就是我說的小問題了,不過影響不大。
其實提到這一段主要想說的是那個 PR,作為一個開源軟件的輸出者,很多細節也是很重要的,這個問題其實很影響源碼的調試,因為對代碼不熟,肯定會一臉懵逼,誰知道是不是哪個后臺線程異步引入了呢。
提這個 PR 的老哥花了兩個小時才搞清楚真正的原因,所以說雖然這不是個 bug 但是很影響那些想深入了解 Dubbo 內部結構的同學們,這種改配置去適應的方案是不可取了,還好最終的方案是改代碼。
好了讓我們回到今天的主題,接下來分析的就是那個不讓我進去的 init 方法了。
源碼分析
init 方法很長,不過大部分就是檢查配置然后將配置構建成 map ,這一大段我就不分析了,我們直接看一下構建完的 map 長什么樣。
然后就進入重點方法 createProxy,從名字可以得到就是要創建的一個代理,因為代碼很長,我就一段一段的分析。
如果是走本地的話,那么直接構建個走本地協議的 URL 然后進行服務的引入,即 refprotocol.refer,這個方法之后會做分析,本地的引入就不深入了,就是去之前服務暴露的 exporterMap 拿到服務。
如果不是本地,那肯定是遠程了,接下來就是判斷是點對點直連 provider 還是通過注冊中心拿到 provider 信息再連接 provider 了,我們分析一下配置了 url 的情況,如果配置了 url 那么不是直連的地址,就是注冊中心的地址。
然后就是沒配置 url 的情況,到這里肯定走的就是注冊中心引入遠程服務了。
最終拼接出來的 URL 長這樣。
可以看到這一部分其實就是根據各種參數來組裝 URL ,因為我們的自適應擴展都需要根據 URL 的參數來進行的。
至此我先畫個圖,給大家先捋一下。
這其實就是整個流程了,簡述一下就是先檢查配置,通過配置構建一個 map ,然后利用 map 來構建 URL ,再通過 URL 上的協議利用自適應擴展機制調用對應的 protocol.refer 得到相應的 invoker 。
在有多個 URL 的時候,先遍歷構建出 invoker 然后再由 StaticDirectory 封裝一下,然后通過 cluster 進行合并,只暴露出一個 invoker 。
然后再構建代理,封裝 invoker 返回服務引用,之后 Comsumer 調用的就是這個代理類。
相信通過圖和上面總結性的簡述已經知道大致的服務引入流程了,不過還是有很多細節,比如如何從注冊中心得到 Provider 的地址,invoker 里面到底是怎么樣的?別急,我們繼續看。
從前面的截圖我們可以看到此時的協議是 registry 因此走的是 RegistryProtocol#refer,我們來看一下這個方法。
主要就是獲取注冊中心實例,然后調用 doRefer 進行真正的 refer。
這個方法很關鍵,可以看到生成了RegistryDirectory 這個 directory 塞了注冊中心實例,它自身也實現了NotifyListener 接口,因此注冊中心的監聽其實是靠這家伙來處理的。
然后向注冊中心注冊自身的信息,并且向注冊中心訂閱了 providers 節點、 configurators 節點 和 routers 節點,訂閱了之后 RegistryDirectory 會收到這幾個節點下的信息,就會觸發 DubboInvoker 的生成了,即用于遠程調用的 Invoker。
然后通過 cluster 再包裝一下得到 Invoker,因此一個服務可能有多個提供者,最終在 ProviderConsumerRegTable 中記錄這些信息,然后返回 Invoker。
所以我們知道Conusmer 是在 RegistryProtocol#refer 中向注冊中心注冊自己的信息,并且訂閱 Provider 和配置的一些相關信息,我們看看訂閱返回的信息是怎樣的。
拿到了Provider的信息之后就可以通過監聽觸發 DubboProtocol# refer 了(具體調用哪個 protocol 還是得看 URL的協議的,我們這里是 dubbo 協議),整個觸發流程我就不一一跟一下了,看下調用棧就清楚了。
終于我們從注冊中心拿到遠程Provider 的信息了,然后進行服務的引入。
這里的重點在 getClients,因為終究是要跟遠程服務進行網絡調用的,而 getClients 就是用于獲取客戶端實例,實例類型為 ExchangeClient,底層依賴 Netty 來進行網絡通信,并且可以看到默認是共享連接。
getSharedClient 我就不分析了,就是通過遠程地址找 client ,這個 client 還有引用計數的功能,如果該遠程地址還沒有 client 則調用 initClient,我們就來看一下 initClient 方法。
而這個connect最終返回 HeaderExchangeClient里面封裝的是 NettyClient 。
然后最終得到的 Invoker就是這個樣子,可以看到記錄的很多信息,基本上該有的都有了,我這里走的是對應的服務只有一個 url 的情況,多個 url 無非也是利用 directory和 cluster再封裝一層。
最終將調用 return (T) proxyFactory.getProxy(invoker); 返回一個代理對象,這個就不做分析了。
到這里,整個流程就是分析完了,不知道大家清晰了沒?我再補充前面的圖,來一個完整的流程給大家再過一遍。
感謝各位的閱讀,以上就是“Dubbo的服務引用過程是什么”的內容了,經過本文的學習后,相信大家對Dubbo的服務引用過程是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。