天天看點

源碼解讀Dubbo分層設計思想

Dubbo是一款非常優秀的分布式服務架構,國内使用非常的廣泛,2018年正式成為apache頂級項目。閱讀本文你将了解到Dubbo的整體分層設計,每一層的意義,以及Dubbo的初始化流程和RPC調用過程,在這個過程涉及到的領域模型Protocol、Invoker、Exporter、Invocation、Result、URL等。本文的特點在于結合源碼詳細的介紹每一層的實際意義。

我們先從下圖開始簡單介紹Dubbo分層設計概念:

源碼解讀Dubbo分層設計思想

(引用自Duboo開發指南-架構設計文檔)

如圖描述Dubbo實作的RPC整體分10層:service、config、proxy、registry、cluster、monitor、protocol、exchange、transport、serialize。

service:使用方定義的接口和實作類; config:負責解析Dubbo定義的配置,比如注解和xml配置,各種參數; proxy:主要負責生成消費者和提供者的代理對象,加載架構功能,比如提供者過濾器鍊,擴充點; registry:負責注冊服務的定義和實作類的裝載; cluster:隻有消費者有這麼一層,負責包裝多個服務提供者成一個‘大提供者’,加載負載均衡、路有等擴充點; monitor:定義監控服務,加載監控實作提供者; protocol:封裝RPC調用接口,管理調用實體的生命周期; exchange:封裝請求響應模式,同步轉異步; transport:抽象傳輸層模型,相容netty、mina、grizzly等通訊架構; serialize:抽象序列化模型,相容多種序列化架構,包括:fastjson、fst、hessian2、kryo、kryo2、protobuf等,通過序列化支援跨語言的方式,支援跨語言的rpc調用;

Dubbo這麼分層的目的在于實作層與層之間的解耦,每一層都定義了接口規範,也可以根據不同的業務需求定制、加載不同的實作,具有極高的擴充性。

接下來結合上圖簡單描述一次完整的rpc調用過程:

從Dubbo分層的角度看,詳細時序圖如下,藍色部分是服務消費端,淺綠色部分是服務提供端,時序圖從消費端一次Dubbo方法調用開始,到服務端本地方法執行結束。

源碼解讀Dubbo分層設計思想

從Dubbo核心領域對象的角度看,我們引用Dubbo官方文檔說明,如下圖所示。Dubbo核心領域對象是Invoker,消費端代理對象是proxy,包裝了Invoker的調用;服務端代理對象是一個Invoker,他通過exporter包裝,當服務端接收到調用請求後,通過exporter找到Invoker,Invoker去實際執行使用者的業務邏輯。

源碼解讀Dubbo分層設計思想

(引用自Dubbo官方文檔)

下圖出自開發指南-架構設計-引用服務時序,主要流程是:從注冊中心訂閱服務提供者,然後啟動tcp服務連接配接遠端提供者,将多個服務提供者合并成一個Invoker,用這個Invoker建立代理對象。

源碼解讀Dubbo分層設計思想

下圖出自開發指南-架構設計-暴露服務時序,主要流程是:建立本地服務的代理Invoker,啟動tcp服務暴露服務,然後将服務注冊到注冊中心。

源碼解讀Dubbo分層設計思想

接下來我們結合Dubbo服務的注冊和發現,從配置層開始解釋每一層的作用和原理。

示例服務接口定義如下:

配置層提供配置處理工具類,在容器啟動的時候,通過ServiceConfig.export執行個體化服務提供者,ReferenceConfig.get執行個體化服務消費者對象。

Dubbo應用使用spring容器啟動時,Dubbo服務提供者配置處理器通過ServiceConfig.export啟動Dubbo遠端服務暴露本地服務。Dubbo服務消費者配置處理器通過ReferenceConfig.get執行個體化一個代理對象,并通過注冊中心服務發現,連接配接遠端服務提供者。

Dubbo配置可以使用注解和xml兩種形式,本文采用注解的形式進行說明。

Spring容器啟動過程中,填充bean屬性時,對含有Dubbo引用注解的屬性使用org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor進行初始化。如下是ReferenceAnnotationBeanPostProcessor的構造方法,Dubbo服務消費者注解處理器處理以下三個注解:DubboReference.class、Reference.class、com.alibaba.dubbo.config.annotation.Reference.class修飾的類。

ReferenceAnnotationBeanPostProcessor類定義:

Dubbo服務發現到這一層,Dubbo即将開始建構服務消費者的代理對象,CouponServiceViewFacade接口的代理實作類。

Spring容器啟動的時候,加載注解@org.apache.dubbo.config.spring.context.annotation.DubboComponentScan指定範圍的類,并初始化;初始化使用dubbo實作的擴充點org.apache.dubbo.config.spring.beans.factory.annotation.ServiceClassPostProcessor。

ServiceClassPostProcessor處理的注解類有DubboService.class,Service.class,com.alibaba.dubbo.config.annotation.Service.class。

如下是ServiceClassPostProcessor類定義:

等待Spring容器ContextRefreshedEvent事件,啟動Dubbo應用服務監聽端口,暴露本地服務。

Dubbo服務注冊到這一層,Dubbo即将開始建構服務提供者的代理對象,CouponServiceViewFacade實作類的反射代理類。

為服務消費者生成代理實作執行個體,為服務提供者生成反射代理執行個體。

CouponServiceViewFacade的代理實作執行個體,消費端在調用query方法的時候,實際上是調用代理實作執行個體的query方法,通過他調用遠端服務。

CouponServiceViewFacade的反射代理執行個體,服務端接收到請求後,通過該執行個體的Invoke方法最終執行本地方法query。

Dubbo代理工廠接口定義如下,定義了服務提供者和服務消費者的代理對象工廠方法。服務提供者代理對象和服務消費者代理對象都是通過工廠方法建立,工廠實作類可以通過SPI自定義擴充。

預設采用Javaassist代理工廠實作,Proxy.getProxy(interfaces)建立代理工廠類,newInstance建立具體代理對象。

Dubbo為每個服務消費者生成兩個代理類:代理工廠類,接口代理類。

CouponServiceViewFacade代理工廠類:

最終生成的CouponServiceViewFacade的代理對象如下,其中handler的實作類是InvokerInvocationHandler,this.handler.invoke方法發起Dubbo調用。

預設Javaassist代理工廠實作,使用Wrapper包裝本地服務提供者。proxy是實際的服務提供者執行個體,即CouponServiceViewFacade的本地實作類,type是接口類定義,URL是injvm協定URL。

Dubbo為每個服務提供者的本地實作生成一個Wrapper代理類,抽象Wrapper類定義如下:

具體Wrapper代理類使用位元組碼技術動态生成,本地服務CouponServiceViewFacade的代理包裝類舉例:

在服務初始化流程中,服務消費者代理對象生成後初始化就完成了,服務消費端的初始化順序:ReferenceConfig.get->從注冊中心訂閱服務->啟動用戶端->建立DubboInvoker->建構ClusterInvoker→建立服務代理對象;

而服務提供端的初始化才剛開始,服務提供端的初始化順序:ServiceConfig.export->建立AbstractProxyInvoker,通過Injvm協定關聯本地服務->啟動服務端→注冊服務到注冊中心。

接下來我們講注冊層。

封裝服務位址的注冊與發現,以服務 URL 為配置中心。服務提供者本地服務啟動成功後,監聽Dubbo端口成功後,通過注冊協定釋出到注冊中心;服務消費者通過注冊協定訂閱服務,啟動本地應用連接配接遠端服務。

注冊協定URL舉例:

zookeeper://xxx/org.apache.dubbo.registry.RegistryService?application=xxx&...

注冊服務工廠接口定義如下,注冊服務實作通過SPI擴充,預設是zk作為注冊中心。

注冊服務接口定義;

服務消費方從注冊中心訂閱服務提供者後,将多個提供者包裝成一個提供者,并且封裝路由及負載均衡政策;并橋接注冊中心,以 Invoker 為中心,擴充接口為 Cluster, Directory, Router, LoadBalance;

服務提供端不存在叢集層。

叢集領域主要負責将多個服務提供者包裝成一個ClusterInvoker,注入路由處理器鍊和負載均衡政策。主要政策有:failover、failfast、failsafe、failback、forking、available、mergeable、broadcast、zone-aware。

叢集接口定義如下,隻有一個方法:從服務目錄中的多個服務提供者建構一個ClusterInvoker。

作用是對上層-代理層屏蔽叢集層的邏輯;代理層調用服務方法隻需執行Invoker.invoke,然後通過ClusterInvoker内部的路由政策和負載均衡政策計算具體執行哪個遠端服務提供者。

ClusterInvoker執行邏輯,先路由政策過濾,然後負載均衡政策選擇最終的遠端服務提供者。示例代理如下:

服務目錄接口定義如下,Dubbo方法接口調用時,将方法資訊包裝成invocation,通過Directory.list過濾可執行的遠端服務。

通過org.apache.dubbo.registry.integration.RegistryDirectory橋接注冊中心,監聽注冊中心的路由配置修改、服務治理等事件。

從已知的所有服務提供者中根據路由規則刷選服務提供者。

服務訂閱的時候初始化路由處理器鍊,調用遠端服務的時候先使用路由鍊過濾服務提供者,再通過負載均衡選擇具體的服務節點。

路由處理器鍊工具類,提供路由篩選服務,監聽更新服務提供者。

訂閱服務的時候,将路由鍊注入到RegistryDirectory中;

根據不同的負載均衡政策從可使用的遠端服務執行個體中選擇一個,負責均衡接口定義如下:

監控RPC調用次數和調用時間,以Statistics為中心,擴充接口為 MonitorFactory, Monitor, MonitorService。

監控工廠接口定義,通過SPI方式進行擴充;

監控服務接口定義如下,定義了一些預設的監控次元和名額項;

通過過濾器的方式收集服務的調用次數和調用時間,預設實作:

org.apache.dubbo.monitor.dubbo.DubboMonitor。

封裝 RPC 調用,以 Invocation, Result 為中心,擴充接口為 Protocol, Invoker, Exporter。

接下來介紹Dubbo RPC過程中的常用概念:

1)Invocation是請求會話領域模型,每次請求有相應的Invocation執行個體,負責包裝dubbo方法資訊為請求參數; 2)Result是請求結果領域模型,每次請求都有相應的Result執行個體,負責包裝dubbo方法響應; 3)Invoker是實體域,代表一個可執行實體,有本地、遠端、叢集三類; 4)Exporter服務提供者Invoker管理實體; 5)Protocol是服務域,管理Invoker的生命周期,提供服務的暴露和引用入口;

服務初始化流程中,從這一層開始進行遠端服務的暴露和連接配接引用。

對于CouponServiceViewFacade服務來說,服務提供端會監聽Dubbo端口啟動tcp服務;服務消費端通過注冊中心發現服務提供者資訊,啟動tcp服務連接配接遠端提供者。

協定接口定義如下,統一抽象了不同協定的服務暴露和引用模型,比如InjvmProtocol隻需将Exporter,Invoker關聯本地實作。DubboProtocol暴露服務的時候,需要監控本地端口啟動服務;引用服務的時候,需要連接配接遠端服務。

Invoker接口定義

Invocation是RPC調用的會話對象,負責包裝請求參數;Result是RPC調用的結果對象,負責包裝RPC調用的結果對象,包括異常類資訊;

服務暴露的時候,開啟RPC服務端;引用服務的時候,開啟RPC用戶端。

接收響應請求;

調用遠端服務;

封裝請求響應模式,同步轉異步,以 Request, Response 為中心,擴充接口為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer。

使用request包裝Invocation作為完整的請求對象,使用response包裝result作為完整的響應對象;Request、Response相比Invocation、Result添加了Dubbo的協定頭。

交換器對象接口定義,定義了遠端服務的綁定和連接配接,使用SPI方式進行擴充;

交換層模型類圖:

源碼解讀Dubbo分層設計思想

服務提供端接收到請求後,本地執行,發送響應結果;

服務消費端發起請求的封裝,方法執行成功後,傳回一個future;

抽象傳輸層模型,相容netty、mina、grizzly等通訊架構。

傳輸器接口定義如下,它與交換器Exchanger接口定義相似,差別在于Exchanger是圍繞Dubbo的Request和Response封裝的操作門面接口,而Transporter更加的底層,Exchanger用于隔離Dubbo協定層和通訊層。

自定義傳輸層模型

源碼解讀Dubbo分層設計思想

通過SPI的方式,動态選擇具體的傳輸架構,預設是netty;

netty架構的channel适配如下,采用裝飾模式,使用netty架構的channel作為Dubbo自定義的channel做實作;

抽象序列化模型,相容多種序列化架構,包括:fastjson、fst、hessian2、kryo、kryo2、protobuf等,通過序列化支援跨語言的方式,支援跨語言的RPC調用。

定義Serialization擴充點,預設hessian2,支援跨語言。Serialization接口實際是一個工廠接口,通過SPI擴充;實際序列化和反序列化工作由ObjectOutput,ObjectInput完成,通過裝飾模式讓hessian2完成實際工作。

下圖出自開發指南-實作細節-遠端通訊細節,描述Dubbo協定頭設計;

源碼解讀Dubbo分層設計思想

0-15bit表示Dubbo協定魔法數字,值:0xdabb;

16bit請求響應标記,Request - 1; Response - 0;

17bit請求模式标記,隻有請求消息才會有,1表示需要服務端傳回響應;

18bit是事件消息标記,1表示該消息是事件消息,比如心跳消息;

19-23bit是序列化類型标記,hessian序列化id是2,fastjson是6,詳見org.apache.dubbo.common.serialize.Constants;

24-31bit表示狀态,隻有響應消息才有用;

32-64bit是RPC請求ID;

96-128bit是會話資料長度;

128是消息體位元組序列;

Dubbo将RPC整個過程分成核心的代理層、注冊層、叢集層、協定層、傳輸層等,層與層之間的職責邊界明确;核心層都通過接口定義,不依賴具體實作,這些接口串聯起來形成了Dubbo的骨架;這個骨架也可以看作是Dubbo的核心,核心使用SPI 機制加載插件(擴充點),達到高度可擴充。

vivo網際網路伺服器團隊-Wang Genfu

分享 vivo 網際網路技術幹貨與沙龍活動,推薦最新行業動态與熱門會議。