很榮幸,作為這樣一款業界使用率和好評率出眾的RPC框架的維護者,今天這個文章主要是想幫助那些熱愛開源的同學,更好的來研究dubbo的源代碼。

 一、Dubbo整體架構

1、Dubbo與Spring的整合
Dubbo在使用上可以做到非常簡單,不管是Provider還是Consumer都可以通過Spring的配置文件進行配置,配置完之后,就可以像使用spring bean一樣進行服務暴露和調用了,完全看不到dubbo api的存在。這是因為dubbo使用了spring提供的可擴展Schema自定義配置支持。在spring配置文件中,可以像、這樣進行配置。META-INF下的spring.handlers文件中指定了dubbo的xml解析類:DubboNamespaceHandler。像前面的被解析成ServiceConfig,被解析成ReferenceConfig等等。
2、jdk spi擴展
由于Dubbo是開源框架,必須要提供很多的可擴展點。Dubbo是通過擴展jdk spi機制來實現可擴展的。具體來說,就是在META-INF目錄下,放置文件名為接口全稱,文件中為key、value鍵值對,value為具體實現類的全類名,key為標志值。由于dubbo使用了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來完成的。接收消息時,拋開dubbo協議的解碼器,調用類的順序是

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的createAdaptiveExtensionClassCode方法。代理類的主要邏輯是,獲取URL參數中指定參數的值作為獲取實現類的key。