dubbo源碼分析
1. 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邏輯;
2. 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
3. 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 方法,其他的拓展框架流程也會明白。只不過關聯的知識點太多了,還是需要時間的積累才能一點一點的搞懂。
如果本篇有描述不清,或者描述有誤的地方,還望在下方留言,大家一起交流,一起學習,一起進步~
4. 【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的屬性上