Dubbo–啓動過程
寫在前面
本文參考了Dubbo官方手冊結合Dubbo2.6.1版本源碼分析。推薦先閱讀官方手冊。
鑑於個人水平有限,如有不正確的地方請指出,歡迎一起討論,謝謝!
啓動過程
啓動過程主要從Provider和Consumer進行分析。每個Provider最終會實例化爲一個ServiceBean的實例,而每個Consumer通過實現自FactoryBean接口的ReferenceBean在首次調用時創建代理實例(也可以通過init屬性設置非延遲加載)。
Provier啓動過程(暴露服務)
Provider的啓動過程默認由afterProperties方法觸發,也可以通過Spring的刷新事件觸發或者定時觸發(通過delay屬性控制),不需要通過代碼getBean即可完成Provider的啓動。
整個啓動過程圍繞兩個URL,一個是調用服務的URL,另一個是同Registry通信的URL。在整個啓動過程中通過向URL添加、讀取屬性(主要是Protocol)、參數,來串聯起整個過程中的擴展(Trasport/Protocol等)的指定和啓動的處理邏輯。
完成啓動步驟(ServiceConfig.doExport()方法)概述如下:
檢查並設置exported屬性
檢查默認配置,即檢查provider屬性是否爲空,爲空則實例化一個。
依照provider->module->application的順序設置自身爲空的屬性(說明自身設置優先級高,且配置優先級privider>module>application)。
檢查Provider提供服務的接口是否有服務方法以及接口實現類是否實現自接口。
檢查application、registry、protocol配置,如果對應對象不存在則進行處理。
對本對象進行屬性設置,屬性值來源於-D或者properties配置文件,優先-D。
檢查stub、local、mock配置,類是非存在、配飾是否合法、執行的構造器是否存在等。
- stub,在客戶端執行,用於客戶端調用服務端時進行調用預處理或異常處理。相當於服務端對客戶端的要求,客戶端在調用時使用的是stub類。
- local,已廢棄,被stub取代。
- mock,在客戶端執行,專門用於調用服務端發生異常時進行異常處理。
設置服務類的路徑
基於各個Registry的配置,構造各個Registry的訪問URL。
- 將application、registry中帶有Parameter註解方法的key、value作爲參數添加到URL中。
- 將application、registry中getParameters方法返回的Map中的key、value作爲參數添加到URL中。
- 將path(訪問路徑)、dubbo(dubbo版本)、timestamp(URL構建時間)添加到URL中。
- URL基本的protocol:IP:port三個部分優先使用registry的值,沒有則使用默認的:dubbo和9090,IP部分必須指定。
- 設置URL的protocol部分爲registry,將實際的protocol以name爲registry,value爲實際protocol值的方式添加到URL參數部分。
根據配置構造服務Bean自身被調用時的訪問URL。
- 添加固定參數:side、dubbo、timestamp、pid
- 將application、module、provider、protocolConfig以及自身中帶有Parameter註解方法的key、value作爲參數添加到URL中。
- 將application、module、provider、protocolConfig中getParameters方法返回的Map中的key、value作爲參數添加到URL中。
- 如果Provider內部有Method配置,則根據method配置添加帶有Parameter註解方法和getParameters方法返回的Map的key、value到URL參數中。
- 嘗試添加可能存在的固定參數:generic、revision、token
- 從Provider的接口中獲取所有的方法,以逗號分隔的方式添加到名爲methods的參數上。此處的參數名爲methods而根據Method配置添加的相關URL參數並不是此參數名。且接口中的方法參數不會體現在URL參數列表中。
根據scope屬性的配置決定代表Provider的URL註冊到Registry還是Local或者兩者都註冊或都不註冊。
如果需要將服務導出爲本地,則進行如下處理:
- 修改Provider的調用URL,修改protocol爲injvm、host爲127.0.0.1、port爲0
- 將Provider實際提供類保存到ThreadLocal中
- 使用JavassistProxyFactory通過Wrapper類動態生成Wrapper類的子類,作爲AbstractProxyInvoker類doInvoke方法的實際處理類
- 由於之前已經將protocol修改爲injvm,所以通過InjvmProtocol的export方法導出,生成InjvmExporter
- 將導出的InjvmExporter添加到ServiceConfig.exporters集合中
如果需要將服務導出到註冊中心,則進行如下處理:
根據配置向Provider調用URL中添加dynamic以及monitor相關參數值
使用JavassistProxyFactory通過Wrapper類動態生成Wrapper類的子類,作爲AbstractProxyInvoker類doInvoke方法的實際處理類。此Invoker和本地導出的Invoker是相同的的類
將上一步返回的Invoker和服務配置包裝爲DelegateProviderMetaDataInvoker類
由於之前JavassistProxyFactory返回的AbstractProxyInvoker中,URL的Protocol爲Registry,所以使用RegistryProtocol的export方法導出,生成DestroyableExporter
RegistryProtocol首先根據服務調用URL調用其對應的Protocol的export打開本地服務,服務處理類根據不同的Protocol包裝爲不同的類,但都最終會調用到之前的Invoker類
Provider Protocol 服務 服務處理類 dubbo netty(grizzly/mima/netty4) – Transport服務擴展 DubboProtocol.ExchangeHandlerAdapter RMI Spring內嵌的RmiServiceExporter 直接使用Invoker類 hessian jetty(Servlet/Tomcat) – HttpBinder服務擴展 HessianProtocol.HessianHandler injvm JVM內部調用 RegistryProtocol內部處理ProviderURL和RegistryURL,刪除不必要的參數等
通過RegistryProtocol內部注入的RegistryFactory(ExtensionLoader注入實現)獲得Registry對象,進而向Registry註冊ProviderURL
Registry Protocol Registry Description dubbo Simple Registry Registry本身是一個RPC服務者,通過文件保存Provider信息 multicast 無 通過使用JDK自帶的廣播功能實現自動發現 redis Redis 通過Redis自身的可靠性保證 zookeeper Zookeeper 通過Zookeeper自身的可靠性保證 mockregistry 無 只是用於測試打樁 使用ProviderConsumerRegTable緩存Provider信息並設置爲已經註冊
向註冊中心訂閱註冊一個監控服務配置改變的監聽器OverrideListener。
Consumer啓動過程(引用服務)
Consumer啓動實際上是ReferenceBean中getObject獲取代理類的過程。ReferenceConfig.init()方法
- 檢查Consumer默認配置,沒有則初始化一個並對屬性賦值。
- 加載服務接口類,並檢查接口類和其內部方法
- 優先嚐試從userHome目錄下的dubbo-resolve.properties文件中根據接口名獲取其調用URL
- 依照consumer->module->application的順序設置自身爲空的屬性(說明自身設置優先級高,且配置優先級consumer>module>application)。
- 檢查application配置
- 檢查stub、local、mock配置,類是非存在、配飾是否合法、執行的構造器是否存在等。
- stub,在客戶端執行,用於客戶端調用服務端時進行調用預處理或異常處理。相當於服務端對客戶端的要求,客戶端在調用時使用的是stub類。
- local,已廢棄,被stub取代。
- mock,在客戶端執行,專門用於調用服務端發生異常時進行異常處理。
- 將application、consumer、module以及side、dubbo版本等信息存放到map中
- 首先判斷是否從JVM本地調用服務,如果是本地,則直接生成本地調用URL以及根據URL生成本地調用Invoker(InjvmInvoker)
- 如果不是本地調用,則判斷之前從dubbo-resolve.properties文件中是否根據接口名加載到了服務URL,如果有則直接到第11步
- 如果沒有從文件中加載到了服務URL,則加載所有的Registry配置獲取所有的Registry訪問URL
- 根據解析出的URL實例化Invoker
- 如果只有一個URL則直接根據URL獲取Protocol,進而向Registry發佈自身相關信息(Consumer、Router),以及訂閱Provider、Configuration、Router信息。最終獲取Invoker(此Invoker內包含Cluster處理方式以及Directory)
- 如果有多個URL則逐個按照一個URL的方式處理,然後將多個Invoker通過StaticDirectory組織後由Cluster組合爲一個Invoker
- 將Cluster組織好的Invoker通過ProxyFactory動態生成Consumer端的代理類,供Consumer調用。這個通過Javassist生成的動態代理類一般情況下會實現EchoService和服務類接口。