當前位置:首頁 » 操作系統 » 深度解剖dubbo源碼

深度解剖dubbo源碼

發布時間: 2023-01-10 06:28:50

㈠ Dubbo(一)——Dubbo 集成於 Spring 的原理

最近一直在看bbo的源碼部分。在閱讀的時候,需要有一個入手點,才能一點一點的進行下去。自己在研究的時候,發現思緒比較亂,於是就以 芋道源碼 為基礎,一點一點的啃食。芋道源碼是直接從bbo的配置和一些核心的API開始講起,是從bbo已經啟動的過程作為開始節點,而這些核心 API 與 Spring 的之間的關系被省略了,這些東西對我來說屬於前置的知識點,所以花了比較長的時間又從 Dubbo 的核心 API 倒著往前看。

在閱讀 Dubbo 時,發現前置知識越來越多,如:Spring 的 refresh 中的一些核心點,Spring 中 bean 的生命周期,BeanFactory 與 FactoryBean 的區別等。所以這些前置知識花了特別多的時間去補。所幸,雖然補前置知識雖然時間長,但是性價比還是可以的。Dubbo 是依賴於Spring 的上下文環境的框架,其他依賴於 Spring 的框架也是相同的道理。Spring 的一些對外的擴展點,讀過之後也會心中有數。

1、本篇主要是描述了 Dubbo 在 Spring 創建上下文的時候,是如何從創建,到能完整提供一個RPC調用能力的一些相關點。
2、由於源碼比較多,直接貼斷點也太過臃腫,所以僅僅貼一些關鍵點來概括整個流程。
3、本文是依賴於前面的 bbo 項目進行斷點分析,項目結構可以參照這里。項目中 bbo 的配置方式是 xml 文件,所以本篇主要說 xml 配置方式。其他方式道理相同,並不是問題的關鍵點。

4、項目啟動的是 bbo-user 服務,所以 UserService 為 bbo:service,OrderService 為 bbo:reference。

下圖為Spring 啟動時是如何載入 Dubbo 的,其中省略了大量過程,只保留了一些關鍵節點,省略的部分可以略微腦補一下。

整個流程的入口是 Spring 的 refresh 方法。每個方法都有比較深的調用棧。與 Dubbo 有關的入口是 refresh 中的 方法

這個方法是執行 beanFactory 的一些後處理操作,其核心流程為在Spring容器中找出實現了BeanFactoryPostProcessor介面的processor並執行。Spring容器會委託給的方法執行。
是比較核心的類,在這里我們關注一下這個類。它的作用是對項目中配置的類進行處理。具體處理可以分為幾步:

在載入類信息時,spring 會去用各種方式掃到注冊的 bean 信息。我們在 spring 中注冊的 bean,逃不出這個方法的掃描方式。 核心方法是:

掃描之後,會將掃描到的 bean 注冊到 beanDefinitionMap 中

首先是此處 org.springframework.beans.factory.xml.#parseBeanDefinitions,可以看出方法會以配置文件根節點起,遍歷所有子節點。

其次是這里 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition), 此方法會通過解析出來的節點,獲取對應的 Spring 的 namespaceUri ,進而獲取對應的配置文件處理器。
此處 ele 參數實際值為 <bbo:service ... />,namespaceUri 為 http://code.alibabatech.com/schema/bbo

我們看一下 resolve 方法中的細節。因為這個方法內部才是 Dubbo 依賴於 Spring 的關鍵點。

此處的 NamespaceHandler 為 DubboNamespaceHandler,再創建結束之後,進行 init 初始化。

可以看到,DubboNamespaceHandler 在初始化的時候,會創建所有 bbo 標簽對應的Config 類的 DubboBeanDefinitionParser。並將 DubboBeanDefinitionParser 和 對應的 bbo 標簽類注冊到 NamespaceHandlerSupport 的 parsers 中。

最後,會在 com.alibaba.bbo.config.spring.schema.DubboBeanDefinitionParser#parse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, java.lang.Class<?>, boolean) 方法中進行處理

Dubbo 服務比較特殊,beanDefinition 跟普通的 bean 不太一樣。在向 beanDefinitionMap 注冊時,普通的 beanDefinition 的 beanName 與 beanClass 是對應的;而 bbo 服務的 beanDefinition 的 beanName 是bbo 服務的名稱,beanClass 為 bbo 對應的 Bean。

普通的 beanDefinition:

bbo 引用的服務的 beanDefinition:

這一步的核心流程是從 beanFactory 中獲取所有的 ApplicationListener,然後注冊到監聽器集合中。它的關鍵點其實是 ServiceBean。因為 ServiceBean 是 ApplicationListener 的實現。

所以 beanFactory 中 ServiceBean 也會被注冊到監聽器集合中。項目中的 ServiceBean 的 beanClass 實際是 UserService。

這一步的核心點,主要是創建剩餘的各類對象,並將其保存到 singletonObjects 中。其中關聯的前置知識為 Spring 中 bean 的生命周期 。它的核心方法是:
org.springframework.beans.factory.support.#doCreateBean

它的具體流程為:

ps:此處並不是只有這一步才會跟 bean 生命周期相關,bean 生命周期貫穿在 refresh 的很多流程中,只要執行doGetBean 方法,都會走這個流程。此處僅僅借樓關聯一下。

這一步的核心點,是通知所有的監聽器上下文刷新結束的事件。在這一步執行時,會通知到 ServiceBean。

此處暴露的是 UserService。

Dubbo 的啟動條件是完全依賴於 Spring 的啟動流程,Spring 的啟動流程中核心的點是 refresh 方法。所以只要搞懂 refresh 方法,其他的拓展框架流程也會明白。只不過關聯的知識點太多了,還是需要時間的積累才能一點一點的搞懂。
如果本篇有描述不清,或者描述有誤的地方,還望在下方留言,大家一起交流,一起學習,一起進步~

㈡ Dubbo之限流分析

在前面的一篇中分析了Dubbo是如何降級的,除了降級,有時限流也是一種很有效的解決高並發的性能問題,那在本篇中開始分析Dubbo是如何限流的。我們知道限流主要是通過控制連接數來實現的,防止某一片段內請求處理過大,導致重要服務的失效。

服務端連接控制

限制當前提供者在使用bbo協議最多接受10個消費者鏈接

或者

並發控制
限制 com.foo.BarService 的每個方法,服務端並發執行(或佔用線程池線程數)不能超過10個:

限制 com.foo.BarService 的 sayHello 方法,伺服器並發執行(或佔用線程池線程數)不能超過10個。

actives限流

該限流方式與前兩種不同,其可以設置在提供端,也可以設置在消費者端。可以設置為介面級別,也可以設置為方法級別。
根據消費者與提供者建立的連接類型,其意義也不同。

長連接 : 表示當前的長連接最多可以處理的請求個數。與長連接的數量沒有問題。
短連接 :表示當前服務可以同時處理的短連接數量。
類級別

方法級別

connections限流

可以設置在提供端,也可以設置在消費者端。限定連接的個數。對於短連接,和actives相同。但對於長連接,表示長連接的個數。
一般情況下,會使connections與actives聯用,讓connections限制長連接的個數,讓actives限制長連接中可以處理的請求個數。
限制客戶端服務使用連接不能超過10個

如果 <bbo:service> 和 <bbo:reference> 都配置了connections, <bbo:reference> 優先。

延遲連接

延遲連接僅可以設置在消費者端,並且不能設置為方法級別。僅作用於Dubbo服務暴露協議。將長連接的建立推遲到消費者真正調用提供者時。 可以減少長連接的數量。

我們已經講解了如何設置控制鏈接數的,那麼它們底層是如何實現的呢?

實際上上面的邏輯都是一個個Filter,所有的Filter會連接成一個過濾器鏈,每次請求都會經過整個鏈路中的每一個Filter。那它是在什麼時候構造成一個過濾器鏈的呢。

在服務暴露的時候會調用 buildInvokerChain , 將真正執行的 invoker 放到過濾鏈的尾部,再執行 protocol.expert(buildInvokerChain(invoker, ...)) 方法來進行服務暴露。

在服務引用的時候會調用 protocol.refer() 方法先生成 Invoker ,再調用 buildInvokerChain(protocol.refer(type, url), ...) 來生成消費類型的調用鏈。

ExecuteLimitFilter

它用於限制每個服務中每個方法的最大並發數,有介面級別和方法級別的配置方式。

其基本原理:在框架中使用一個ConcurrentMap緩存了並發數的計數器,為每個請求URL生成一個IdentityString,並以此為key;再將每個IdentityString生成一個RpcStatus對象,將此作為value。RpcStatus對象用於記錄對應的並發數。在調用開始之前,會通過URL獲得RpcStatus對象,把對象中的並發數計數器原子+1,在finally中再將原子減1。只要在計數器+1的時候,發現當前計數器比設置的並發數大時,就會拋出異常。

TpsLimitFilter

TpsLimitFilter的限流是基於令牌的,即一段時間內只分配N個令牌,每次請求都會消耗一個令牌,耗完為止,後面再來的請求都會被拒絕。
具體的邏輯是在 DefaultTPSLimiter#isAllowable ,會用這個方法判斷是否觸發限流。
在DefaultTPSLimiter內部用一個ConcurrentHashMap緩存每個介面的令牌數,key是interface+group+version,value是一個StatItem對象,它包裝了令牌刷新時間間隔、每次發放的令牌數等。首先判斷當前時間減去上次發放令牌的時間是否超過了時間間隔,超過了就重新發放令牌,之前剩餘的令牌會被直接覆蓋掉。然後,通過CAS的方式減去1令牌,減掉後小於0就會觸發限流。

ActiveLimitFilter

和服務提供者的 ExecuteLimitFilter 相似,它是消費者端的過濾器,限制的是客戶端的並發量。

但是它與 ExecuteLimitFilter 有所不同,它不會直接拋出異常。而是當到達閾值的時候,會先加鎖搶占當前介面的RpcStatus對象,然後通過wait方法進行等待,等待是有時間的,因為請求是有 timeout 屬性的。然後如果某個Invoker在調用結束後,並發把計數器減-1並觸發一個notify,此時會有一個在wait狀態的線程被喚醒並繼續執行,判斷現在是否超時,如果超時則拋出異常。如果當前並發數仍然超出閾值,則繼續執行wait方法;如果沒有超出閾值在,則跳出循環,CAS+1,並調用invoke方法,調用結束後CAS-1,最後通過notify喚醒另外一個線程。

參考文章:
Dubbo之限流TpsLimitFilter源碼分析
Dubbo服務限流
Dubbo源碼分析----過濾器之ActiveLimitFilter

㈢ 【bbo源碼】13. 服務消費方之@Reference依賴注入原理

用法 :

當某個主要注冊到spring容器中的bean 的屬性上有@Reference註解時,並且 註解的injvm = false時(默認),表明該屬性會被注入一個遠程介面實例,用作rpc遠程調用。

@Reference不是派生自spring默認支持的@Resource和@Autowired,那麼說明spring是不支持該註解用於依賴注入的,bbo對此進行了支持該注冊的拓展。

在入口@EnableDubbo配置了掃描的包路徑
用於掃描類上含有bbo@Service 和類屬性上含有 @Reference 的bean

@DubboComponentScan 導入了DubboComponentScanRegistrar

DubboComponentScanRegistrar實現Spring的ImportBeanDefinitionRegistrar介面,被調用到實現的registerBeanDefinitions()時,會注冊兩個beanPostProcessor類

實現了和spring中支持@Autowired注冊的一模一樣的介面 :,繼承了。兩者的工作邏輯幾乎一模一樣。

在bean實例化之後,對 bean進行依賴注入

postProcessPropertyValues()

這個方法每個bean實例化都會調到,用的是父類的

判斷方法和類上是否有@Reference註解,並將其包包裝成.AnnotatedInjectionMetadata 對象,進行緩存

判斷屬性是否可以獲取到@Reference,獲取到返回

收集到需要依賴注入的屬性之後,下一步獲取到被依賴的bean實例,進行反射賦值

調用內部類AnnotatedFieldElement中的inject方法

獲取到bbo創建的代理實例,反射設置有@Reference的屬性上

㈣ 【bbo源碼】5.配置信息解析-註解版

用於把bbo.properties讀到spring的environment中,

這個工作是由Spring的類來完成的.檢測到某個需要注冊的Bean上有@PropertySource註解,就會讀該文件的配置信息,弄到environment對象的MutablePropertySources對象中。

後期會把配置信息弄到bbo 配置類中.

該註解上還有@DubboComponentScan,@EnableDubboConfig,這兩個註解是bbo用註解與spring集成的核心了

該註解用@import導入了這個類

實現了ImportBeanDefinitionRegistrar介面,那麼spring在實例化的時候會調用重寫ImportBeanDefinitionRegistrar介面的registerBeanDefinitions方法,並且將用@Import導入的類的元數據包裝成importingClassMetadata對象。

其實就是為了獲取入口類AnnoBean上的@EnableDubboConfig註解里的multiple屬性配置的值,默認是true.

然後注冊了兩個DubboConfigConfiguration的內部類

通過讀Class對象注冊到ioc容器

類上有@EnableDubboConfigBindings,值為@EnableDubboConfigBinding數組
通過綁定,將有對應前綴的配置信息賦值到對應的配置類中

又用@Import導入DubboConfigBindingsRegistrar類,DubboConfigBindingsRegistrar這個類又實現了ImportBeanDefinitionRegistrar,EnvironmentAware介面

實現ImportBeanDefinitionRegistrar肯定是為了另外導入一些類,並且拿到導入的源類,獲取源類上配置的信息

實現EnvironmentAware是為了拿到spring的environment對象,因為 bbo.properties 已經被@PropertySource註解機制載入到了environment.MutablePropertySources中,在這里只對beanName的創建有作用。

registrar.registerBeanDefinitions :

注冊的過程中,需要從environment對象中拿bbo相關的配置,比如ApplicationConfig只拿
bbo.application.*相關的配置,然後創建ApplicationConfig的BeanDefinition.

如果 @EnableDubboConfigBinding配置的multiple為true(默認為false),並且在配置文件中配置了同樣前綴的屬性,如:

這樣會為同一種配置類型,生成兩個BD.beanName不同的配置Bean,名稱規則如下所示, #0表示的是'.'在配置的key中出現的位置

之後還會注冊一個BeanPostProcessor類型的類的beanDefinition,BeanPostProcessor類型 會在每一個Bean實例化的過程中,根據配置的前綴,從environment拿出所需的配置,根據beanName來處理beanName相同的這一個配置Bean,把配置信息綁定到配置類的屬性中。

.

利用 bboConfigBinder 對象來綁定前綴為bbo.application的配置信息到配置Bean中

這里bboConfigBinder對象是中的一個屬性,是在因為這個類實現了InitializingBean這個介面的afterPropertiesSet方法,bboConfigBinder對象就是在這里初始化的

最後用的DataBinder的api把一個MutablePropertyValues綁定到Bean的屬性

@import進來了DubboComponentScanRegistrar類

DubboComponentScanRegistrar又實現了ImportBeanDefinitionRegistrar介面,實現registerBeanDefinitions方法.

跟xml的邏輯一樣,同樣是

㈤ 14. bbo源碼-集群容錯之MergeableCluster

在bbo官方的用戶手冊中,提到了使用 MergeableCluster 的場景--分組聚合:

功能示意圖如下:

定義菜單介面方式:

Provider暴露服務--一個服務屬於 group-hot ,一個服務屬於 group-cold

筆者測試時啟動了兩個Provider,所以總計有四個服務,bbo-monitor監控顯示如下:

Consumer調用服務:

幾個重要的配置說明:

com.alibaba.bbo.rpc.cluster.Merger 文件內容如下:

核心源碼在 MergeableClusterInvoker.java 中,源碼如下所示:

在條件分支 if ( merger.startsWith(".") ) {} 中,有一段邏輯: method = returnType.getMethod( merger, returnType ); ,即從bbo服務介面方法返回類型即 java.util.List 中查找merger配置的方法,例如 .addAll ,我們先看一下debug過程各變數的值:

bbo源碼中 method = returnType.getMethod( merger, returnType ); 調用 Method method = getMethod0(name, parameterTypes, true); ,再調用 Method res = privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates); ,最後調用 searchMethods(privateGetDeclaredMethods(true), name, parameterTypes)) ,得到最後方法匹配的核心邏輯如下:

從searchMethods()源碼可知,方法匹配需要滿足幾個條件:

由上面的分析可知,如果要merger=".addAll"能夠正常工作,那麼只需要將bbo服務的返回類型改成 Collection 即可,例如:

如果 com.alibaba.bbo.rpc.cluster.Merger 文件集中方法無法滿足需求,需要自定義實現,那麼還是和bbo其他擴展實現一樣,依賴SPI。只需要一下幾步實現即可:

㈥ Dubbo之服務導出源碼分析

要了解服務導出做了什麼,需要了解導出的目的是什麼?
bbo是一款面向介面代理的高性能RPC調用,說白了就是提供遠程服務。

服務導出需要做的:簡單說就是根據服務參數、服務協議構建服務URL,注冊到注冊中心,並啟動Server。其中服務參數可以動態配置也需要監聽。

1、onApplicationEvent
org.apache.bbo.config.spring.ServiceBean#onApplicationEvent
發布ContextRefreshedEvent,進行導出服務

2、export
org.apache.bbo.config.spring.ServiceBean#export
調用父類ServiceConfig導出,之後發布一個ServiceBeanExportedEvent

②、shouldExport檢查服務是否需要導出
org.apache.bbo.config.ServiceConfig#shouldExport

org.apache.bbo.config.ServiceConfig#checkAndUpdateSubConfigs

1、completeCompoundConfigs
ServiceConfig中的某些屬性如果是空的,那麼就從ProviderConfig、MoleConfig、ApplicationConfig中獲取
①、如果配置了ProviderConfig provider,如果application、mole、registries、monitor、protocols、configCenter這些屬性為空的情況下,可以從provider中獲取信息並賦值

③、如果ApplicationConfig application不為空,可以為registries、monitor為空的賦值
2、startConfigCenter
從配置中心獲取配置,包括應用配置和全局配置

SystemConfiguration:是系統環境變數,可以在服務啟動時通過-D指定參數
AbstractConfig:是通過@Service註解配置的參數
PropertiesConfiguration:是bbo.properties文件配置的
AppExternalConfiguration和ExternalConfiguration:是在配置中心Dubbo-Admin中配置的,AppXxx是應用級別
配置優先順序是:SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
如果放到第二個位置優先順序是:SystemConfiguration -->AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> -> PropertiesConfiguration
c、根據setXX()方法和setParameters()方法進行參數值覆蓋

②、prepareEnvironment
管理台上的動態配置中心,如果是zookeeper,獲取的就是/bbo/config/bbo/bbo.properties節點中的內容。如果是應用級別的則獲取的就是/bbo/config/bbo-demo-consumer-application/bbo.properties節點中的內容(bbo-demo-consumer-application應用名,這里是以bbo-demo為例)

4、checkProtocol 從配置中心設置Protocol配置

10、Local、Stub、Mock
local和stub一樣,不建議使用了。如果Local存根為true,則存根類為interfaceName + "Local"。如果Stub本地存根為true,則存根類為interfaceName + "Stub"

org.apache.bbo.config.ServiceConfig#doExport

unexported表示:當前服務已經被取消了,就不需要再導出了
exported表示:已經導出了,就不再重復導出了
1、doExportUrls
org.apache.bbo.config.ServiceConfig#doExportUrls

②、遍歷protocols協議
pathKey = group/應用名/path服務名:version
例如:mygroup/bbo-demo/org.apache.bbo.demo.DemoService:1.0.1
ProviderMethodModel表示某一個方法、方法名所屬的服務的,包含實現類,介面,以及介面中的各個方法

ApplicationModel表示應用中有哪些服務提供者和引用了哪些服務
每種協議都會導出一個單獨的服務,並注冊到各個注冊中心
2、doExportUrlsFor1Protocol
org.apache.bbo.config.ServiceConfig#doExportUrlsFor1Protocol

③、methods方法參數處理

⑦、scope導出方式(scope=null進行遠程導出)

生成的注冊服務URL是在registryURL和export參數是服務url的拼裝
b、也表示服務提供者,包括了Invoker和服務的配置。是對Invoker的包裹
c、protocol.export(wrapperInvoker)這是導出的核心,使用了SPI。
使用特定的協議來對服務進行導出,這里的協議是registry,導出成功後得到一個Exporter
由於注冊地址也是服務,所以會先使用RegistryProtocol進行服務注冊(服務導出),然後注冊(服務導出)完了之後,再使用DubboProtocol進行導出
自適應SPI會獲取url
org.apache.bbo.config.invoker.#getUrl

⑩、當存在注冊中心時,是先使用Registy協議注冊服務,然後在使用Http協議導出服務。而沒有注冊中心時,是直接使用Http協議導出服務。根據服務url,講服務的元信息存入元數據中心MetadataReportService

RegistryProtocol被ProtocolFilterWrapper包裹,ProtocolFilterWrapper被ProtocolListenerWrapper包裹(SPI原理)

1、export
org.apache.bbo.rpc.protocol.ProtocolListenerWrapper#export
如果協議是REGISTRY_PROTOCOL(registry)則會直接走到下一個protocol實現類ProtocolFilterWrapper。
如果不是則會使用ListenerExporterWrapper處理

②、獲取providerUrl 服務提供者url

③、生成overrideSubscribeUrl,與監聽有關
在服務提供者url的基礎上,生成一個overrideSubscribeUrl,協議為provider://,增加參數category=configurators&check=false
overrideSubscribeUrl是用來對動態配置監聽的,需要監聽的服務和監聽的類型(configurators, 老版本)
一個overrideSubscribeUrl對應一個OverrideListener,用來監聽overrideSubscribeUrl變化事件

④、doLocalExport導出
此時服務協議是bbo,DubboProtocol被ProtocolFilterWrapper包裹,ProtocolFilterWrapper被ProtocolListenerWrapper包裹

1、export
org.apache.bbo.rpc.protocol.ProtocolListenerWrapper#export
返回結果被ListenerExporterWrapper包裹

2、export
org.apache.bbo.rpc.protocol.ProtocolFilterWrapper#export

EchoFilter、ClassLoaderFilter、GenericFilter、ContextFilter、TraceFilter、TimeoutFilter、MonitorFilter、ExceptionFilter
返回包裹了Filter的CallbackRegistrationInvoker
3、export
org.apache.bbo.rpc.protocol.bbo.DubboProtocol#export

②、register
org.apache.bbo.registry.support.FailbackRegistry#register

在服務導出過程中,需要對動態配置中心的數據進行監聽。如果發生更改,可以及時做出反映。
這里用到了Zookeeper的Watcher機制。
1、配置中心的路徑與動態配置路徑是不相同的,這里需要區分開來
配置中心的路徑是:
①、應用:/bbo/config/bbo/org.apache.bbo.demo.DemoService/bbo.properties節點的內容
②、全局:/bbo/config/bbo/bbo.properties節點的內容
2、對於動態配置的監聽有版本的差別
在2.7之前,只可以對單個服務進行監聽,不可以對應用監聽
/bbo/org.apache.bbo.demo.DemoService/configurators/* 只對路徑名字監聽,不監聽內容
在2.7之後,服務和應用都可以監聽
服務: /bbo/config/bbo/org.apache.bbo.demo.DemoService.configurators 節點的內容
應用: /bbo/config/bbo/bbo-demo-provider-application.configurators 節點的內容
3、監聽時機RegistryProtocol#export
org.apache.bbo.registry.integration.RegistryProtocol#export

㈦ Dubbo啟動源碼解析一

這次講 bbo-spring-boot-starter 啟動方式,所以入口就是Spring的SPI機制;
首先在META-INF/spring.factories配置下,配置了org.apache.bbo.spring.boot.autoconfigure.DubboAutoConfiguration類,在啟動時,則會把DubboAutoConfiguration類注冊到spring容器中;
我們來看下DubboAutoConfiguration
先看啟動流程

我們先看下生產者端的啟動流程,首先是在Spring中注冊類

該類實現了介面,則在Spring容器初始化時,會調用方法

我們會看到,這個時候會去注冊類,這個類我們等流程到了在分析,我們先按啟動流程看過去;resolvePackagesToScan方法先獲取到需要掃描的包 ,然後再調用registerServiceBeans去注冊相關實例,我們重點來看下registerServiceBeans方法

接下來,我們主要去看下registerServiceBean方法

接下來,我們來看下buildServiceBeanDefinition方法

到這,ServiceBean注冊成功,ServiceBean類很重要,每個Dubbo service實例都對應一個ServiceBean,相關配置都在ServiceBean中;我們再回到開始注冊的類

類繼承了,實現了ApplicationListener,主要監聽了Spring容器生命周期,我們看下onApplicationContextEvent方法

我們可以看到,當Spring容器啟動成功時,會調用bboBootstrap.start();

接下來,主要邏輯在ServiceBean中,這個export方法在其父類ServiceConfig中,我們下一篇主要講ServiceConfig邏輯;

㈧ 如何更好地學習bbo源代碼

一、Dubbo整體架構
1、Dubbo與Spring的整合
Dubbo在使用上可以做到非常簡單,不管是Provider還是Consumer都可以通過Spring的配置文件進行配置,配置完之後,就可以像使用spring
bean一樣進行服務暴露和調用了,完全看不到bbo
api的存在。這是因為bbo使用了spring提供的可擴展Schema自定義配置支持。在spring配置文件中,可以像、這樣進行配置。META-INF下的spring.handlers文件中指定了bbo的xml解析類:DubboNamespaceHandler。像前面的被解析成ServiceConfig,被解析成ReferenceConfig等等。
2、jdk spi擴展
由於Dubbo是開源框架,必須要提供很多的可擴展點。Dubbo是通過擴展jdk
spi機制來實現可擴展的。具體來說,就是在META-INF目錄下,放置文件名為介面全稱,文件中為key、value鍵值對,value為具體實現類的全類名,key為標志值。由於bbo使用了url匯流排的設計,即很多參數通過URL對象來傳遞,在實際中,具體要用到哪個值,可以通過url中的參數值來指定。
Dubbo對spi的擴展是通過ExtensionLoader來實現的,查看ExtensionLoader的源碼,可以看到Dubbo對jdk spi做了三個方面的擴展:

(1)jdk spi僅僅通過介面類名獲取所有實現,而ExtensionLoader則通過介面類名和key值獲取一個實現;

(2)Adaptive實現,就是生成一個代理類,這樣就可以根據實際調用時的一些參數動態決定要調用的類了。

(3)自動包裝實現,這種實現的類一般是自動激活的,常用於包裝類,比如Protocol的兩個實現類:ProtocolFilterWrapper、ProtocolListenerWrapper。
3、url匯流排設計
Dubbo為了使得各層解耦,採用了url匯流排的設計。我們通常的設計會把層與層之間的交互參數做成Model,這樣層與層之間溝通成本比較大,擴展起來也比較麻煩。因此,Dubbo把各層之間的通信都採用url的形式。比如,注冊中心啟動時,參數的url為:
registry://0.0.0.0:9090?codec=registry&transporter=netty
這就表示當前是注冊中心,綁定到所有ip,埠是9090,解析器類型是registry,使用的底層網路通信框架是netty。

二、Dubbo啟動過程

Dubbo分為注冊中心、服務提供者(provider)、服務消費者(consumer)三個部分。
1、注冊中心啟動過程
注冊中心的啟動過程,主要看兩個類:RegistrySynchronizer、RegistryReceiver,兩個類的初始化方法都是start。
RegistrySynchronizer的start方法:

(1)把所有配置信息load到內存;

(2)把當前注冊中心信息保存到資料庫

(3)啟動5個定時器。
5個定時器的功能是:
(1)AutoRedirectTask,自動重定向定時器。默認1小時運行1次。如果當前注冊中心的連接數高於平均值的1.2倍,則將多出來的連接數重定向到其他注冊中心上,以達到注冊中心集群的連接數均衡。
(2)DirtyCheckTask,臟數據檢查定時器。作用是:分別檢查緩存provider、資料庫provider、緩存consumer、資料庫consumer的數據,清除臟數據;清理不存活的provider和consumer數據;對於緩存中的存在的provider或consumer而資料庫不存在,重新注冊和訂閱。
(3)ChangedClearTask,changes變更表的定時清理任務。作用是讀取changes表,清除過期數據。
(4)AlivedCheckTask,注冊中心存活狀態定時檢查,會定時更新registries表的expire欄位,用以判斷注冊中心的存活狀態。如果有新的注冊中心,發送同步消息,將當前所有注冊中心的地址通知到所有客戶端。
(5)ChangedCheckTask,變更檢查定時器。檢查changes表的變更,檢查類型包括:參數覆蓋變更、路由變更、服務消費者變更、權重變更、負載均衡變更。
RegistryReceiver的start方法:啟動注冊中心服務。默認使用netty框架,綁定本機的9090埠。最後啟動服務的過程是在NettyServer來完成的。接收消息時,拋開bbo協議的解碼器,調用類的順序是
NettyHandler-》NettyServer-》MultiMessageHandler-》HeartbeatHandler-》AllDispatcher-》
DecodeHandler-》HeaderExchangeHandler-》RegistryReceiver-》RegistryValidator-》RegistryFailover-》RegistryExecutor。

2、provider啟動過程
provider的啟動過程是從ServiceConfig的export方法開始進行的,具體步驟是:
(1)進行本地jvm的暴露,不開放任何埠,以提供injvm這種形式的調用,這種調用只是本地調用,不涉及進程間通信。
(2)調用RegistryProtocol的export。
(3)調用DubboProtocol的export,默認開啟20880埠,用以提供接收consumer的遠程調用服務。
(4)通過新建RemoteRegistry來建立與注冊中心的連接。
(5)將服務地址注冊到注冊中心。
(6)去注冊中心訂閱自己的服務。
3、consumer啟動過程
consumer的啟動過程是通過ReferenceConfig的get方法進行的,具體步驟是:
(1)通過新建RemoteRegistry來建立與注冊中心的連接。
(2)新建RegistryDirectory並向注冊中心訂閱服務,RegistryDirectory用以維護注冊中心獲取的服務相關信息。
(3)創建代理類,發起consumer遠程調用時,實際調用的是InvokerInvocationHandler。

三、實際調用過程
consumer端發起調用時,實際調用經過的類是:
1、consumer:

InvokerInvocationHandler-》MockClusterInvoker(如果配置了Mock,則直接調用本地Mock類)-》FailoverClusterInvoker(負載均衡,容錯機制,默認在發生錯誤的情況下,進行兩次重試)-》RegistryDirectory$InvokerDelegete-》ConsumerContextFilter-》FutureFilter->DubboInvoker

2、provider:
NettyServer-》MultiMessageHandler-》HeartbeatHandler-》AllDispatcher-》DecodeHandler-》HeaderExchangeHandler-》DubboProtocol.requestHandler-》EchoFilter-》ClassLoaderFilter-》GenericFilter-》ContextFilter-》ExceptionFilter-》TimeoutFilter-》MonitorFilter-》TraceFilter-》實際service。

四、Dubbo使用的設計模式
1、工廠模式
ServiceConfig中有個欄位,代碼是這樣的:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

Dubbo里有很多這種代碼。這也是一種工廠模式,只是實現類的獲取採用了jdk

spi的機制。這么實現的優點是可擴展性強,想要擴展實現,只需要在classpath下增加個文件就可以了,代碼零侵入。另外,像上面的Adaptive實現,可以做到調用時動態決定調用哪個實現,但是由於這種實現採用了動態代理,會造成代碼調試比較麻煩,需要分析出實際調用的實現類。
2、裝飾器模式
Dubbo在啟動和調用階段都大量使用了裝飾器模式。以Provider提供的調用鏈為例,具體的調用鏈代碼是在ProtocolFilterWrapper的buildInvokerChain完成的,具體是將註解中含有group=provider的Filter實現,按照order排序,最後的調用順序是
EchoFilter-》ClassLoaderFilter-》GenericFilter-》ContextFilter-》ExceptionFilter-》
TimeoutFilter-》MonitorFilter-》TraceFilter。

更確切地說,這里是裝飾器和責任鏈模式的混合使用。例如,EchoFilter的作用是判斷是否是回聲測試請求,是的話直接返回內容,這是一種責任鏈的體現。而像ClassLoaderFilter則只是在主功能上添加了功能,更改當前線程的ClassLoader,這是典型的裝飾器模式。
3、觀察者模式
Dubbo的provider啟動時,需要與注冊中心交互,先注冊自己的服務,再訂閱自己的服務,訂閱時,採用了觀察者模式,開啟一個listener。注冊中心會每5秒定時檢查是否有服務更新,如果有更新,向該服務的提供者發送一個notify消息,provider接受到notify消息後,即運行NotifyListener的notify方法,執行監聽器方法。
4、動態代理模式
Dubbo擴展jdk

spi的類ExtensionLoader的Adaptive實現是典型的動態代理實現。Dubbo需要靈活地控制實現類,即在調用階段動態地根據參數決定調用哪個實現類,所以採用先生成代理類的方法,能夠做到靈活的調用。生成代理類的代碼是ExtensionLoader的方法。代理類的主要邏輯是,獲取URL參數中指定參數的值作為獲取實現類的key。

熱點內容
安卓按鍵大師怎麼用 發布:2025-05-15 01:54:12 瀏覽:686
手機ea伺服器連不上怎麼辦 發布:2025-05-15 01:35:03 瀏覽:450
資料庫數據插入語句 發布:2025-05-15 01:30:01 瀏覽:871
js是無需編譯直接運行嗎 發布:2025-05-15 01:28:30 瀏覽:476
android文件夾重命名 發布:2025-05-15 01:13:50 瀏覽:481
cns腳本 發布:2025-05-15 01:13:38 瀏覽:722
數據結構與演算法筆試題 發布:2025-05-15 01:04:20 瀏覽:417
搜狗輸入法如何直接編輯配置文件 發布:2025-05-15 00:51:47 瀏覽:668
電箱都有哪些配置 發布:2025-05-15 00:30:21 瀏覽:74
安卓qq邀請碼在哪裡尋找 發布:2025-05-15 00:02:04 瀏覽:35