隨著互聯網的發展和網站規模的擴大,系統架構也從單點的垂直結構往分散式服務架構演進,如下圖所示:
當服務比較少時,可以通過 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
各層關係說明:
Dubbo核心領域模型:
Dubbo主要包括以下幾個節點:
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:
① 定義服務介面:
② 服務提供者代碼:
ServiceConfig 類拿到對外提供服務的實際類 ref(如:DemoServiceImpl)通過 ProxyFactory.getInvoker 方法使用 ref 生成一個 AbstractProxyInvoker 實例,然後 通過 Protocol.export 方法新生成一個 Exporter 實例
當網路通訊層收到一個請求後,會找到對應的 Exporter 實例,並調用它所對應的 AbstractProxyInvoker 實例,從而真正調用了服務提供者的代碼
③ 服務消費者代碼:
首先通過 ReferenceConfig.init 方法調用 Protocal.refer 方法生成 Invoker 實例,接下來通過 ProxyFactory.getProxy 方法將 Invoker 轉換為客戶端需要的介面(如:DemoService)
DemoService 就是 consumer 端的 proxy,用戶代碼通過這個 proxy 調用其對應的 Invoker,通過 Invoker 實現真正的遠程調用
四. 功能特性
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提供了多種容錯方案:
3. 路由規則
路由規則決定一次dubbo服務調用的目標伺服器,分為腳本路由規則和條件路由規則,支持可擴展。向註冊中心寫入路由規則的操作通常由治理中心的頁面完成
4. 負載均衡
如上圖 LoadBlance 模塊所示:在集群負載均衡時,Dubbo提供了不同的策略:
5. 線程派發模型
如果事件處理的邏輯能迅速完成,並且不發生新的IO請求(例如在內存中記個標識),則在IO線程上處理更快,因為減少了線程池調度
如果事件處理的邏輯較慢,或需要發起新的IO請求(例如需要查詢資料庫),則必須派發到線程池,否則 IO 線程阻塞,將導致不能接受其他請求
因此需要不同的派發策略和不同的線程池組合來應對不同的場景:
Dispatcher:
ThreadPool:
6. 上下文信息和隱式參數
上下文中存放著當前調用過程中所需的環境信息。RpcContext 是一個 ThreadLocal 的臨時狀態記錄器,當接收或發起 RPC 請求時,RpcContext 都會發生變化。比如:A調用B,B調用C,在B調C之前,B機器上 RpcContext 記錄的是A調用B的信息。
通過 RpcContext 的 setAttachment 和 getAttachment 可以在 provider 和 consumer 之間進行參數的隱式傳遞
基於NIO的非阻塞實現並行調用,客戶端不需要啟動多線程即可完成多個遠程服務的並行調用,相對比多線程開銷較小
對於 provider,它需要發布服務,而且由於應用系統的複雜性,服務的數量、類型也不斷膨脹;對於 consumer,它最關心如何獲取到它所需要的服務,而面對複雜的應用系統,需要管理大量的服務調用
服務註冊中心通過特性協議將服務統一管理起來,有效的優化內部應用對服務發布/使用的流程。Dubbo提供的註冊中心有如下幾種類型可供選擇:
① ZooKeeper註冊中心
ZK是一個樹形的服務目錄,支持變更推送,適合作為Dubbo服務的註冊中心。流程如下:
當 provider 出現斷電等異常停機時,註冊中心能自動刪除 provider 信息。當註冊中心重啟、或會話過期時,能自動恢復註冊數據和訂閱請求
② Multicase註冊中心
Multicast註冊中心不需要啟動任何中心節點,只要廣播地址即可互相發現
組播受網路結構限制,只適合小規模應用或開發階段
③ Redis註冊中心
使用 redis 的 Key/Map 結構存儲數據結構:
調用過程:
大家可以關注我的公眾號:《Java爛豬皮》,平常我也會發寫技術文章,比如:架構,分散式,微服務spring,jvm,MySQL等知識點。面試經驗也會分享給大家。
http://weixin.qq.com/r/9Sg5If3EZKAbrblC933n (二維碼自動識別)
在此謝謝大家的關注支持~~~~