Dubbo2.7.3版本原始碼學習系列六: Dubbo服務匯出原始碼解析

AvengerEug發表於2020-11-01

前言

一、官網對服務匯出的簡介

  • 官網描述

    Dubbo 服務匯出過程始於 Spring 容器釋出重新整理事件,Dubbo 在接收到事件後,會立即執行服務匯出邏輯。整個邏輯大致可分為三個部分,第一部分是前置工作,主要用於檢查引數,組裝 URL。第二部分是匯出服務,包含匯出服務到本地 (JVM),和匯出服務到遠端兩個過程。第三部分是向註冊中心註冊服務,用於服務發現。

  • 通過官網的描述,我們們可以知道服務匯出的原始碼開始於Spring的重新整理事件(這裡對Spring容器重新整理事件不太瞭解的,可以參考此篇文章spring 5.0.x原始碼學習系列十: 觀察者設計模式與Spring 事件驅動模型)。Dubbo在進行服務匯出時,會做三個步驟:

    1、前置工作:主要用於檢查引數、組裝URL <=====> 類似於Spring在初始化bean時定義的一系列BeanDefinition

    2、匯出服務:包括服務匯出到本地和匯出到遠端

    3、服務註冊:向註冊中心註冊當前被匯出的服務

二、前置工作:檢查引數、組裝URL

  • 在Dubbo做前置工作(檢查引數、組裝URL)之前,Dubbo得先把當前服務對應的一些配置從配置檔案或Spring 配置類中解析出來,其次才是執行所謂的前置工作。在這一個個過程中,用到了spring的許多過程點,在這裡,我將用到的spring擴充套件點及其作用都一一羅列了出來。

2.1 Dubbo服務匯出整合Spring的第一個擴充套件點NamespaceHandlerSupport`針對xml而言)

  • 我們都知道,使用Dubbo來整合spring時,官方提供給我們兩種方式:

    第一:基於註解

    第二:基於XML

    基於註解的解析,在之前的Dubbo2.7.3版本原始碼學習系列三: Dubbo註解版本載入配置原理文章中有總結到。這裡就不過多解釋了。

    基於XML的解析,這裡簡單說明下:

    • 以xml的格式來啟動spring上下文,其中我們可以在xml中寫如下類似的標籤:

      <dubbo:service id="test" interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
      

      這種標籤,最終會被spring給解析出來,會對應ServiceBean類。這樣的結果是有地方配置的,詳看org.apache.dubbo.config.spring.schema.DubboNamespaceHandler類。其中主要看它的init方法,如下:

    在這裡插入圖片描述

    在init方法中,當我們配置**<dubbo:service />**類似的標籤時,spring會解析我們的配置併為對應的型別建立BeanDefinition。(每一個dubbo:service標籤對應一個ServiceBean型別的BeanDefinition)。

  • 因此,通過第一個擴充套件點後,我們在xml配置的資訊已經變成Spring的BeanDefinition了,為spring構建bean建立的條件。

2.2 Dubbo服務匯出整合Spring的第二個擴充套件點BeanPostProcessor

  • 先讚歎下Spring的BeanPostProcessor擴充套件點,它真的太牛逼了。可以對bean做任何處理,包括代理自定義填充屬性等等。其中如果這個bean是實現了不同Aware介面,都會進行回撥。其中對於處理aware的入口為ApplicationContextAwareProcessor類,在此類的postProcessBeforeInitialization方法中會對各種aware做處理。換言之就是:我們可以通過ApplicationContextAwareProcessor類來獲取spring上下文的許多東西,原始碼如下:

    在這裡插入圖片描述

  • 此擴充套件點的作用比較簡單,就是為bean填充內部需要的相關aware介面。那ServiceBean來舉例,ServiceBean主要實現了ApplicationContextAware和ApplicationEventPublisherAware介面,因此只是為了填充內部的ApplicationEventPublisherApplicationContext屬性,僅此而已。

2.3 Dubbo服務匯出整合Spring的第三個擴充套件點InitializingBean

  • 此擴充套件點在dubbo中就比較重要了,它是利用此擴充套件點來填充額外的一些配置,比如我們在xml中配置的如下標籤

    <dubbo:application name="demo-provider"/>
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:protocol name="dubbo"/>
    

    最終都會通過此後置處理器將ServiceBean(拿ServiceBean舉例)內部的一些protocols、application、module、registries等配置屬性給填充,相當於把當前服務與其他共享的配置給關聯起來,進而知道當前服務屬於哪個應用程式、共用哪些協議。當然前提還是得配置好且被spring解析到。在Dubbo這些配置類中,都會存在一個叫ConfigManager的類中,此類為單例(懶漢式),內部存放了當前dubbo應用程式的所有配置,其中包括共用的配置和每個服務私有的配置。

  • 因此,通過Spring的InitializingBean擴充套件點,我們可以將當前暴露的服務與其他配置相關聯,比如當前暴露出去的服務支援哪些協議、使用哪個註冊中心、屬於哪個應用程式

2.4 Dubbo服務匯出整合Spring的第四個擴充套件點ApplicationListener

  • 在ServiceBean中,它還實現了ApplicationListener介面。這代表著這個類對spring的ContextRefreshedEvent事件感興趣(詳見org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)方法,當spring容器初始化後,會發布ContextRefreshedEvent事件,此時就會通知所有訂閱了此事件的監聽者)。同時,在Dubbo服務暴露的開發者文件中也有提到,Dubbo服務暴露的核心就是Spring容器的重新整理事件,如下為Dubbo官網的原話,點此連結檢視

    Dubbo 服務匯出過程始於 Spring 容器釋出重新整理事件,Dubbo 在接收到事件後,會立即執行服務匯出邏輯

    因此,上述三個擴充套件點都是為Dubbo服務匯出奠定了基礎,服務匯出的第一步不是要檢查引數麼?要檢查,首先得把我們們配置的資訊給解析出來嘛,不然檢查什麼呢?

2.5 從Spring的事件驅動模型擴充套件點開始,正式進入Dubbo的服務匯出邏輯

  • 根據我們最開始的總結,服務匯出分為如下三步:

    1、前置工作:主要用於檢查引數、組裝URL  <=====>  類似於Spring在初始化bean時定義的一系列BeanDefinition
    2、匯出服務:包括服務匯出到本地和匯出到遠端 
    3、服務註冊:向註冊中心註冊當前被匯出的服務
    
  • 其服務匯出入口如下:

    在這裡插入圖片描述

    PS:Dubbo中使用了大量的責任鏈設計模式,可以提前瞭解下它,否則在閱讀原始碼的過程中會遇到一些困難

2.5.1 前置條件的第一步:檢查配置

  • 檢查配置的細節在這裡就不詳細說了,大致的就是檢查使用者配置的合法性以及應用程式沒有相關配置時,則使用預設值。同時會開啟註冊中心功能,具體的邏輯可參考org.apache.dubbo.config.ServiceConfig#checkAndUpdateSubConfigs方法。

2.5.2 前置條件的第一步:組裝URL

  • 組裝URL的過程建議參考官方文件2.1.3 組裝 URL章節。這裡以如下配置為例描述下生成的URL

    在這裡插入圖片描述

    在組裝URL的過程中,每個URL將會對應一個操作,比如我們在匯出服務時,只需要將一些描述服務的基本資訊放置在URL中即可。比如,我們需要將服務往註冊中心註冊時,URL中必須包含跟註冊中心相關的資訊才行。因此,在這個過程中,URL是一直在變化的,這個變化的情況視當前要操作的業務邏輯而定。比如,在進行服務匯出時,URL的變化為如下原始碼註釋所示:

    在這裡插入圖片描述
    其中服務匯出整體的URL結構如下所示:

    在這裡插入圖片描述

    此URL代表著當前服務的一些基本資訊,比如它位於哪一個應用程式(application變數),位於哪一臺主機上(host變數),在spring中對應的bean的名稱(bean.name變數),服務的型別(interface),包含哪些方法(methods)。而在將服務註冊到註冊中心時,URL又是另外一些模樣。這裡等到服務註冊步驟時再羅列出來。

  • 組裝URL的步驟比較麻煩,建議仔細閱讀官網,因為URL針對Dubbo而言超級重要,以下文字來自於官網:

    URL 是 Dubbo 配置的載體,通過 URL 可讓 Dubbo 的各種配置在各個模組之間傳遞。URL 之於 Dubbo,猶如水之於魚,非常重要。
    

    官網都給我們們開後門了,就差壓著我們們學習URL了,還不加緊把URL整明白?

三、匯出服務

3.1 為匯出的服務構建Invoker物件

  • URL組裝好了之後,我們們就可以大膽的將服務匯出了。

  • 為匯出的服務構建Invoker物件的程式碼還是在doExportUrlsFor1Protocol方法中,匯出服務包含兩個:本地匯出遠端匯出

  • 在本例中,我們配置的註冊中心只有一個,就是我們們配置的registry標籤。

    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    

    因此,當前服務需要被註冊到註冊中心的URL將被解析成如下模樣:

    在這裡插入圖片描述

  • 為上述配置的服務生成Invoker物件

    在這裡插入圖片描述
    這裡我們要注意下:構建Invoker物件時,傳入進去的URL並不是我們最開始構建的URL(描述當前服務的URL),而是根據註冊中心配置的URL,那麼我們之前構建的URL有什麼用呢?有用的,我們可以在構建Invoker物件時看到,url最終會變成一個字串(url.toFullString())新增到registruURL中去,也就是說,Invoker中的URL,主體是當前遍歷的協議的內容,只不過內部加了一個叫key為**EXPORT_KEY(export)**的引數,其中value為當前暴露出服務的整個URL(即最開始組裝的URL)。如下圖所示(registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())程式碼的執行結果):

    在這裡插入圖片描述
    因此,我們應該能大概明白invoker物件中url屬性的結構了。

3.2 RegistryProtocol作為匯出服務到遠端的入口

  • 同樣的,服務匯出的入口也在doExportUrlsFor1Protocol方法中,其原始碼如下:

    在這裡插入圖片描述
    由註釋中的分析可知,我們們把視角挪到org.apache.dubbo.registry.integration.RegistryProtocol#export

    在這裡插入圖片描述
    由上述的原始碼註釋可知:RegistryProtocol的export方法中主要做了兩件事情:

    1、利用服務匯出的URL進行匯出服務
    2、向註冊中心註冊服務
    

3.3 根據服務提供者的配置執行真實的服務匯出邏輯

  • 真實的服務匯出邏輯,我們把程式碼定位到:org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport,其原始碼如下:

    在這裡插入圖片描述

    正如上述原始碼中的註釋所示,我們忽略Protocol的Wrapper類,直接把程式碼定位到DubboProtocl的export方法

  • org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export原始碼如下:

    在這裡插入圖片描述

    真實的匯出邏輯比較簡單,就是開啟了一個netty伺服器以及為invoker構建了一個export物件。此段邏輯執行結束後,我們們再把目光放到org.apache.dubbo.registry.integration.RegistryProtocol#export處,我們最開始是從org.apache.dubbo.registry.integration.RegistryProtocol#export內部的doLocalExport方法進入,完成服務匯出邏輯,並返回一個ExporterChangeableWrapper物件。

3.4 匯出服務總結

  • 內容比較簡單,主要做了如下兩件事:
    1. 為匯出的服務構建invoker物件,此invoker物件比較特殊,以註冊中心的url為主體,描述服務的url將作為引數儲存在invoker中。
    2. 解析invoker物件,拿到註冊中心的url以及描述服務的url,再針對描述服務的url來執行真正的服務匯出邏輯(走具體的服務匯出協議,以及開啟對應的伺服器)
  • 按照官網的流程來說的話,服務匯出結束了,剩下的就是服務註冊流程了

四、服務註冊

  • 服務註冊的流程我們們繼續接著org.apache.dubbo.registry.integration.RegistryProtocol#export進行分析,其原始碼如下:

    在這裡插入圖片描述
    在註冊服務的過程中,最終會呼叫register方法,其內部的程式碼大家看了之後應該就能明白了:

    在這裡插入圖片描述

  • 服務註冊流程比較簡單了,就是自適應擴充套件類registryFactory根據url中的protocol屬性來決定使用哪個註冊中心來完成服務註冊。

五、總結

  • 整個服務匯出過程是嚴格按照官網的流程來進行總結的,把官方沒有描述的一些技術給完善了。要了解服務匯出的底層原理,首先得把自適應擴充套件機制、ProxyFactory、Wrapper類搞明白,還有個最重要的是要時刻關注URL的變化,因為很多自適應擴充套件機制的程式碼,程式碼沒有變,但因為url的某個引數發生了變化,就變成另外一個類來執行邏輯了。
  • 如果你覺得我的文章有用的話,歡迎點贊和關注。?
  • I’m a slow walker, but I never walk backwards

相關文章