Dubbo架構學習整理


一. Dubbo誕生背景

隨着互聯網的發展和網站規模的擴大,系統架構也從單點的垂直結構往分布式服務架構演進,如下圖所示:

  • 單一應用架構:一個應用部署所有功能,此時簡化CRUD的ORM框架是關鍵
  • 垂直應用架構:應用拆分為不相干的幾個應用,前后端分離,此時用於加速前端頁面開發的Web MVC框架是關鍵
  • 分布式服務架構:抽取各垂直應用的核心業務作為獨立服務,形成穩定的服務中心,此時用於提高業務復用及整合的分布式服務框架(RPC)是關鍵
  • 流動計算架構:當服務越來越多,容量的評估、小服務資源的浪費等問題逐漸顯現,此時用於提高機器利用率的實時資源調度和治理中心(SOA)是關鍵

 

當服務比較少時,可以通過 RMI 或 Hession 等工具,簡單的暴露和引用遠程服務,通過配置服務的URL地址來調用,通過F5等硬件負載均衡

當服務越來越多時,服務配置URL變的困難,F5硬件負載均衡的單點壓力越來越大。此時需要服務注冊中心,動態的注冊和發現服務,使服務的位置透明。服務調用實現軟負載均衡和Failover,降低對F5硬件負載均衡器的依賴

當服務間關系越來越復雜時,此時需要自動畫出服務間的依賴關系圖,來幫助架構師理清服務關系

當服務調用量越來越大時,服務需要多少台機器支撐,服務容量的問題就暴露出來了,此時需要統計服務每天的調用量、響應時間等性能指標作為容量規划的參考。其次,還可以動態調整權重,將某台機器權重一直加大,直到響應時間到閥值,按照此時的訪問量反推服務的總容量

以上是Dubbo的基本需求,如下圖所示:

二. 整體架構

Dubbo的整體架構設計如圖所示:

 

Dubbo框架一共分10層,各層單向依賴。最上面的 Service 和 Config 為API,其他均為 SPI。左邊淡藍色的為 consumer 使用的接口,右邊淡綠色的為 provider 使用的接口,中間的為雙方都用到的接口。

黑色箭頭代表層之間的依賴關系;藍色虛線為初始化過程,即啟動時組裝鏈;紅色實線為方法調用過程;紫線為繼承關系。線上的文字為調用的方法。

1、接口服務層(Service):該層與業務邏輯相關,根據 provider 和 consumer 的業務設計對應的接口和實現

2、配置層(Config):對外配置接口,以 ServiceConfig 和 ReferenceConfig 為中心

3、服務代理層(Proxy):服務接口透明代理,生成服務的客戶端 Stub 和 服務端的 Skeleton,以 ServiceProxy 為中心,擴展接口為 ProxyFactory

4、服務注冊層(Registry):封裝服務地址的注冊和發現,以服務 URL 為中心,擴展接口為 RegistryFactory、Registry、RegistryService

5、路由層(Cluster):封裝多個提供者的路由和負載均衡,並橋接注冊中心,以Invoker 為中心,擴展接口為 Cluster、Directory、Router和LoadBlancce

6、監控層(Monitor):RPC調用次數和調用時間監控,以 Statistics 為中心,擴展接口為 MonitorFactory、Monitor和MonitorService

7、遠程調用層(Protocal):封裝 RPC 調用,以 Invocation 和 Result 為中心,擴展接口為 Protocal、Invoker和Exporter

8、信息交換層(Exchange):封裝請求響應模式,同步轉異步。以 Request 和 Response 為中心,擴展接口為 Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer

9、網絡傳輸層(Transport):抽象 mina 和 netty 為統一接口,以 Message 為中心,擴展接口為Channel、Transporter、Client、Server和Codec

10、數據序列化層(Serialize):可復用的一些工具,擴展接口為Serialization、 ObjectInput、ObjectOutput和ThreadPool 

各層關系說明:

  • Portocol 是核心層,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 調用,然后在 Invoker 的主過程上 Filter 攔截點
  • Cluster 是外圍概念,目的是將多個 Invoker 偽裝為一個 Invoker,這樣其它人只要關注 Protocol 層 Invoker 即可。只有一個 provider 時,是不需要 Cluster 的
  • Proxy 層封裝了所有接口的透明化代理,而在其它層都以 Invoker 為中心,只有到了暴露給用戶使用時,才用 Proxy 將 Invoker 轉成接口,或將接口實現轉成 Invoker,看起來像調本地服務一樣調遠程服務
  • Remoting 內部再划為 Transport 傳輸層和 Exchange 信息交換層:Transport 層只負責單向消息傳輸,是對 Mina, Netty, Grizzly 的抽象;而 Exchange 層是在傳輸層之上封裝了 Request-Response 語義

Dubbo核心領域模型:

  • Protocol 是服務域,它是 Invoker 暴露和引用的主功能入口,它負責 Invoker 的生命周期管理
  • Invoker 是實體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉換成它。它代表一個可執行體,可向它發起 invoke 調用,它有可能是一個本地的實現,也可能是一個遠程的實現,也可能一個集群實現
  • Invocation 是會話域,它持有調用過程中的變量,比如方法名,參數等

Dubbo主要包括以下幾個節點:

  • Provider:暴露服務的服務提供方
  • Consumer:調用遠程服務的服務消費方
  • Registry:服務注冊和發現的注冊中心
  • Monitor:統計服務的調用次數和調用時間的監控中心
  • Container:服務運行容器

Consumer, Provider, Registry, Monitor代表邏輯部署節點。圖中只包含 RPC 層,不包含 Remoting層,Remoting整體隱藏在 Protocol 中。

藍色方框代表業務有交互,綠色方框代表只對Dubbo內部交互。藍色虛線為初始化時調用,紅色虛線為運行時異步調用,紅色實線為運行時同步調用

0、服務在容器中啟動,加載,運行Provider

1、Provider在啟動時,向Registry注冊自己提供的服務

2、Consumer在啟動時,想Registry訂閱自己所需的服務

3、Registry給Consumer返回Provider的地址列表,如果Provider地址有變更(上線/下線機器),Registry將基於長連接推動變更數據給Consumer

4、Consumer從Provider地址列表中,基於軟負載均衡算法,選一台進行調用,如果失敗,重試另一台調用

5、Consumer和Provider,在內存中累計調用次數和時間,定時每分鍾一次將統計數據發送到Monitor

將上面的服務調用流程展開,如下圖所示:

藍色虛線為初始化過程,即啟動時組裝鏈;紅色實線為方法調用過程,即運行時調用鏈;紫色實線為繼承

 

三、實現細節

Invoker 是 Dubbo 領域模型中非常重要的一個概念,很多設計思路都是向它靠攏,這就使得 Invoker 滲透在整個實現代碼里。下面用一個精簡的圖來說明最重要的兩種 Invoker:服務提供 Invoker 和服務消費 Invoker:

① 定義服務接口:

public interface DemoService {
    String sayHello(String name);
}

② 服務提供者代碼: 

public class DemoServiceImpl implements DemoService {
    public String sayHello(String name) {
        return "Hello " + name;
    }
}

ServiceConfig 類拿到對外提供服務的實際類 ref(如:DemoServiceImpl)通過 ProxyFactory.getInvoker 方法使用 ref 生成一個 AbstractProxyInvoker 實例,然后 通過 Protocol.export 方法新生成一個 Exporter 實例

當網絡通訊層收到一個請求后,會找到對應的 Exporter 實例,並調用它所對應的 AbstractProxyInvoker 實例,從而真正調用了服務提供者的代碼

③ 服務消費者代碼:

public class DemoClientAction {
   
  private DemoService demoService;

  public void setDemoService(DemoService demoService) {
    this.demoService = demoService;
  }

  public void start() {
    String hello = demoService.sayHello("world");
  } }

首先通過 ReferenceConfig.init 方法調用 Protocal.refer 方法生成 Invoker 實例,接下來通過 ProxyFactory.getProxy 方法將 Invoker 轉換為客戶端需要的接口(如:DemoService)

DemoService 就是 consumer 端的 proxy,用戶代碼通過這個 proxy 調用其對應的 Invoker,通過 Invoker 實現真正的遠程調用

 

四. 功能特性

1. 配置

Dubbo可以采用全Spring的配置方式,基於Spring的Schema擴展進行加載,接入對業務透明,無API侵入。配置項可參考:schema 配置參考手冊

除了Spring配置,也可以使用API配置、屬性配置和注解配置方式。

配置之間的關系,如下圖所示:

provider side:

  • <dubbo:protocol/>:協議配置。用於配置提供服務的協議信息,協議由provider指定,consumer被動接受
  • <dubbo:service/>: 服務配置。暴露一個service,定義service的元信息,一個service可以用多個協議暴露,也可以注冊到多個注冊中心
  • <dubbo:provider/>:提供方配置【可選】。當 ProtocolConfig 和 ServiceConfig 某屬性沒有配置時,采用此缺省值

consumer side:

  • <dubbo:reference/>:引用配置。用於創建一個遠程服務代理,一個引用可以指向多個注冊中心
  • <dubbo:consumer/>:消費方配置【可選】。當 ReferenceConfig 某屬性沒有配置時,采用此缺省值

application shared:

  • <dubbo:application/>:應用配置。配置應用信息,包括provider和consumer
  • <dubbo:registry/>:注冊中心配置。配置連接注冊中心相關信息
  • <dubbo:monitor/>:監控中心配置【可選】。配置連接監控中心相關信息

sub-config:

  • <dubbo:method/>:方法配置。用於 ServiceConfig 和 ReferenceConfig 指定方法級的配置信息
  • <dubbo:argument/>:參數配置。用於指定方法參數配置

 

2. 集群容錯

服務調用時的過程如下圖:

 

Invoker:是Provider的一個可調用Service的抽象,封裝了Provider地址和Service接口信息

Directory:代表多個Invoker,可將它看為List<Invoker>,它的值是動態變化的,比如注冊中心推送變更

Cluster:將Directory的多個Invoker偽裝為一個Invoker,對上層透明。偽裝過程中包括容錯邏輯,例如:一個Invoker調用失敗后重試另一個Invoker

Router:從多個Invoker中按路由規則選出子集,例如:讀寫分離、應用隔離等

LoadBlance:從多個Invoker中選出具體的一個Invoker用於本次調用,選的過程包括負載均衡算法,調用失敗后需要重選

當Cluster集群調用失敗時,Dubbo提供了多種容錯方案:

  • Failover【默認】:失敗時自動切換,重試其它服務器。通常用於讀操作,可通過 retries="2" 來設置重試次數(不含第一次)
  • Failfast:快速失敗,只調用一次,失敗立即報錯。通常用於非冪等的寫操作,比如:新增記錄
  • Failsafe:失敗安全,失敗時直接忽略。通常用於寫入審計日志等操作
  • Failback:失敗自動恢復,后台記錄失敗請求,定時重發。通常用於消息通知等操作
  • Forking:並行調用多個服務器,只要一個成功即返回。通常用於實時性較高的讀操作,但浪費更多服務資源。可通過 forks="2" 設置最大並行數
  • Broadcast:廣播調用者,逐個調用,任意一台報錯則報錯。通常用於通知所有提供者更新本地資源信息,如緩存、日志等

 

3. 路由規則

路由規則決定一次dubbo服務調用的目標服務器,分為腳本路由規則和條件路由規則,支持可擴展。向注冊中心寫入路由規則的操作通常由治理中心的頁面完成

  • 腳本路由規則:支持JDK腳本引擎的所有腳本,例如:javascript, groovy 等
  • 條件路由規則:基於條件的路由規則,例如:host = 10.20.153.10 => hsot = 10.20.153.11。=>之前是consumer匹配條件,所有參數和consumer的URL進行對比,如果consumer滿足匹配條件,則對consumer執行后面的過濾規則。=>之后是provider地址列表的過濾條件,所有參數和provider的URL進行對比,consumer只拿到過濾后的地址列表

 

4. 負載均衡

如上圖 LoadBlance 模塊所示:在集群負載均衡時,Dubbo提供了不同的策略:

  • Random【默認】:隨機,按權重設置隨機概率。調用量越大越均勻,有利於動態調整權重
  • RoundRobin:輪詢,按公約后的權限設置輪詢比率。如果有台機器很慢,但沒掛,當請求到那一台時就卡在那兒,久而久之,所有請求都卡在那台機器上
  • LeastActive:最少活躍調用數,活躍數指調用前后計數差,越慢的provider的調用前后計數差越大,使得慢的provider收到更少請求
  • ConsistentHash:一致性Hash,相同參數的請求發往同一台provider,當一台provider掛掉時,原本發往該機器的請求,基於虛擬節點會平攤到其他機器,不會引起劇烈變動

 

5. 線程派發模型 

如果事件處理的邏輯能迅速完成,並且不發生新的IO請求(例如在內存中記個標識),則在IO線程上處理更快,因為減少了線程池調度

如果事件處理的邏輯較慢,或需要發起新的IO請求(例如需要查詢數據庫),則必須派發到線程池,否則 IO 線程阻塞,將導致不能接受其他請求

因此需要不同的派發策略和不同的線程池組合來應對不同的場景:

Dispatcher:

  • all:所有消息派發到 ThreadPool,包括請求、響應、連接事件、斷開事件、心跳等
  • direct:所有消息不派發 ThreadPool,全在 IO 線程上執行
  • message:只有請求響應消息派發到 ThreadPool,其他連接事件、斷開事件、心跳等,在 IO 線程上執行
  • execution:只請求消息派發到 ThreadPool,其他事件包括響應事件、連接斷開事件、心跳等消息,在 IO 線程上執行
  • connection:在 IO 線程上,將連接斷開事件放入隊列,有序逐個執行,其他時間派發到 ThreadPool

ThreadPool:

  • fixed【默認】:固定大小線程池,啟動時建立線程,一直持有不關閉
  • cached:緩存線程池,空閑一分鍾自動刪除,需要時重建
  • limited:可伸縮線程池,線程數只增長不收縮,目的是為了避免收縮時大流量引起的性能問題
  • eager:優先創建Worker線程池,corePoolSize < 任務數量 < maximumPoolSize時,優先創建 Worker 處理任務。任務數量 > maximumPoolSize時,任務放入阻塞隊列中,阻塞隊列充滿時拋出 RejectExecutionException

 

6. 上下文信息和隱式參數

上下文中存放着當前調用過程中所需的環境信息。RpcContext 是一個 ThreadLocal 的臨時狀態記錄器,當接收或發起 RPC 請求時,RpcContext 都會發生變化。比如:A調用B,B調用C,在B調C之前,B機器上 RpcContext 記錄的是A調用B的信息。

通過 RpcContext 的 setAttachment 和 getAttachment 可以在 provider 和 consumer 之間進行參數的隱式傳遞

 

7. 異步調用

基於NIO的非阻塞實現並行調用,客戶端不需要啟動多線程即可完成多個遠程服務的並行調用,相對比多線程開銷較小

 

8. 注冊中心

對於 provider,它需要發布服務,而且由於應用系統的復雜性,服務的數量、類型也不斷膨脹;對於 consumer,它最關心如何獲取到它所需要的服務,而面對復雜的應用系統,需要管理大量的服務調用

服務注冊中心通過特性協議將服務統一管理起來,有效的優化內部應用對服務發布/使用的流程。Dubbo提供的注冊中心有如下幾種類型可供選擇:

① ZooKeeper注冊中心

ZK是一個樹形的服務目錄,支持變更推送,適合作為Dubbo服務的注冊中心。流程如下:

  • provider啟動時,向 /dubbo/com.foo.BarService/providers 目錄下寫入自己的 URL 地址
  • consumer啟動時,訂閱 /dubbo/com.foo.BarService/providers 目錄下的 providers 地址,並向 /dubbo/com.foo.BarService/consumers 目錄下寫入自己的 URL 地址
  • 監控中心啟動時,訂閱 /dubbo/com.foo.BarService 目錄下的所有 provider 和 consumer URL地址

當 provider 出現斷電等異常停機時,注冊中心能自動刪除 provider 信息。當注冊中心重啟、或會話過期時,能自動恢復注冊數據和訂閱請求

② Multicase注冊中心

Multicast注冊中心不需要啟動任何中心節點,只要廣播地址即可互相發現

  • provider 啟動時廣播自己的地址
  • consumer 啟動時廣播訂閱請求
  • provider 收到訂閱請求時,單播自己的地址給訂閱者,若設置了 unicast=false,則廣播給訂閱者
  • consumer 收到 provider 地址時,連接地址進行 RPC 調用

組播受網絡結構限制,只適合小規模應用或開發階段

③ Redis注冊中心

使用 redis 的 Key/Map 結構存儲數據結構:

  • 主 Key 為服務名和類型
  • Map 中的 Key 為 URL 地址
  • Map 中的 Value 為過期時間,用於判斷臟數據,臟數據由監控中心刪除

調用過程:

  1. provider 啟動時,向 Key:/dubbo/com.foo.BarService/providers 下,添加當前 provider 的地址
  2. 並向 Channel:/dubbo/com.foo.BarService/providers 發送 register 事件
  3. consumer 啟動時,向 Key:/dubbo/com.foo.BarService/providers 下,添加當前 consumer 的地址
  4. 並從 Channel:/dubbo/com.foo.BarService/providers 訂閱 register 和 unregister 事件
  5. consumer 收到 register 和 unregister 事件后,從 Key:/dubbo/com.foo.BarService/providers 下獲取 provider 地址列表
  6. 服務監控中心啟動時,從 Channel:/dubbo/* 訂閱 register 和 unregister,以及 subscribe 和 unsubscribe 事件
  7. 監控中心收到 register 和 unregister 事件后,從 Key:/dubbo/com.foo.BarService/providers 下獲取 provider 地址列表
  8. 監控中心收到 subscribe 和 unsubscribe 事件后,從 Key:/dubbo/com.foo.BarService/comsumers 下獲取 consumer 地址列表

 

參考

Dubbo開發文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM