高性能可擴展分佈式RPC框架Dubbo-內核原理揭祕

1、前言

總體來講,一個公司業務系統的演進流程基本都是從單體應用到多體應用。在單體應用時,不一樣業務模塊相互調用直接在本地 JVM 進程內就能夠完成;而變爲多個應用時,相互之間進行通訊的方式就不能簡單的進行本地調用了,由於不一樣業務模塊部署到了不一樣的 JVM 進程裏面,更常見的是部署到了不一樣的機器,這時候一個高效、穩定的 RPC 遠程調用框架就變得很是重要。html

Dubbo做爲阿里巴巴開發的一個開源的高性能的RPC調用框架,其致力於提供高性能和透明化的 RPC 遠程調用服務解決方案。做爲阿里巴巴 SOA 服務化治理方案的核心框架,目前它已進入 Apache 孵化器頂級項目,前景可謂無限光明。java

2、Dubbo-基礎篇

2.1 Dubbo系統組成概述

使用Dubbo框架搭建的系統架構以下: ios

image.png

如上圖是 Dubbo 的架構圖,其中:算法

  • 服務提供方在啓動時候會註冊本身提供的服務到服務註冊中心。sql

  • 服務消費方在啓動時候會去服務註冊中心訂閱本身須要的服務的地址列表,而後服務註冊中心異步把消費方須要的服務接口的提供者的地址列表返回給服務消費方,服務消費方根據路由規則和設置的負載均衡算法選擇一個服務提供者 IP 進行調用。數據庫

  • 監控平臺主要用來統計服務的調用次數和調用耗時,服務消費者和提供者,在內存中累計調用次數和調用耗時,並定時每分鐘發送一次統計數據到監控中心,監控中心則使用數據繪製圖表來顯示,監控平臺不是分佈式系統必須的,可是這些數據有助於系統運維和調優。服務提供者和消費者能夠直接配置監控平臺的地址,也能夠經過服務註冊中心來獲取。apache

  • 服務註冊中心則負責服務註冊與發現,常見的服務註冊中心有zookeeper、etcd。編程

2.2 Dubbo基礎

本節主要簡單的講解Dubbo如何使用,以及本書中的demo實例,建議讀者先閱讀基礎篇在進入後面的章節,由於後面章節基本是基於本章的demo進行講解的。安全

demo中 Consumer 模塊爲服務消費者相關,本書中全部與消費端有關的demo都在該模塊中,包含普通調用、各類異步調用、泛化調用、基於擴展接口實現的自定義負載均衡策略、集羣容錯策略等等。bash

其中 Provider 模塊爲服務提供者相關,本書中全部與服務提供端有關的demo都在該模塊中,包含服務接口的實現類、服務提供方的同步處理請求、各類異步處理請求的實現等等。

其中 SDK 模塊是一個二方包,用來存放服務接口,這是爲了代碼複用,在服務提供者和消費者(泛化調用除外)的模塊裏面都須要引入這個二方包。

3、Dubbo-高級篇

3.1 Dubbo分層架構

本節咱們從總體上來看看 Dubbo 的分層架構設計,架構分層是一個比較經典的模式,好比網絡中的7層協議,每層執行固定的功能,上層依賴下層提供的功能,下層對上層的提供功能,下層的改變對上層不可見,而且每層都是一個可被替換的組件。

以下圖是 Dubbo 官方提供的Dubbo的總體架構圖:

image.png

Dubbo 官方提供的該架構圖很複雜,一開始咱們不必深刻細節,下面咱們簡單講解下其中的主要模塊:

  • 其中 Service 和 Config 層爲 API接口層,是爲了方便的讓Dubbo使用方發佈服務和引用服務;對於服務提供方來講須要實現服務接口,而後使用 ServiceConfig API 來發布該服務;對於服務消費方來講須要使用ReferenceConfig 對服務接口進行代理。Dubbo服務發佈與引用方能夠直接初始化配置類,也能夠經過 Spring 配置自動生成配置類。

  • 其它各層均爲 SPI層,SPI 意味着下面各層都是組件化能夠被替換的,這也是 Dubbo 設計的比較好的一點。Dubbo 加強了 JDK 中提供的標準 SPI 功能,在 Dubbo 中除了 Service 和 Config 層外,其它各層都是經過實現擴展點接口來提供服務的;Dubbo 加強的 SPI 增長了對擴展點 IoC 和 AOP 的支持,一個擴展點能夠直接 setter 注入其它擴展點;而且不會一次性實例化擴展點的全部實現類,這避免了當擴展點實現類初始化很耗時,但當前還沒用上它的功能時仍進行加載實例化,浪費資源的狀況;加強的 SPI 是在具體用某一個實現類的時候纔對具體實現類進行實例化。後續會具體講解 Dubbo 加強的 SPI 的實現原理。

  • Proxy 服務代理層:該層主要是對服務消費端使用的接口進行代理,把本地調用透明的轉換爲遠程調用;另外對服務提供方的服務實現類進行代理,把服務實現類轉換爲 Wrapper 類,這是爲了減小反射的調用,後面會具體講解到。Proxy層的SPI擴展接口爲 ProxyFactory,Dubbo 提供的實現主要有 JavassistProxyFactory(默認使用)和 JdkProxyFactory,用戶能夠實現ProxyFactory SPI接口,自定義代理服務層的實現。

  • Registry 服務註冊中心層:服務提供者啓動時候會把服務註冊到服務註冊中心,消費者啓動時候會去服務註冊中心獲取服務提供者的地址列表,Registry層主要功能是封裝服務地址的註冊與發現邏輯,擴展接口 Registry 對應的擴展實現爲 ZookeeperRegistry、RedisRegistry、MulticastRegistry、DubboRegistry等。擴展接口 RegistryFactory 對應的擴展接口實現爲 DubboRegistryFactory、DubboRegistryFactory、RedisRegistryFactory、ZookeeperRegistryFactory。另外該層擴展接口Directory實現類有RegistryDirectory、StaticDirectory用來透明的把invoker列表轉換爲一個invoker;用戶能夠實現該層的一系列擴展接口,自定義該層的服務實現。

  • Cluster 路由層:封裝多個服務提供者的路由規則、負載均衡、集羣容錯的實現,並橋接服務註冊中心;擴展接口 Cluster 對應的實現類有 FailoverCluster(失敗重試)、FailbackCluster(失敗自動恢復)、FailfastCluster(快速失敗)、FailsafeCluster(失敗安全)、ForkingCluster(並行調用)等;負載均衡擴展接口 LoadBalance 對應的實現類爲 RandomLoadBalance(隨機)、RoundRobinLoadBalance(輪詢)、LeastActiveLoadBalance(最小活躍數)、ConsistentHashLoadBalance(一致性hash)等。用戶能夠實現該層的一系列擴展接口,自定義集羣容錯和負載均衡策略。

  • Monitor 監控層:用來統計RPC 調用次數和調用耗時時間,擴展接口爲 MonitorFactory,對應的實現類爲 DubboMonitorFactroy。用戶能夠實現該層的MonitorFactory擴展接口,實現自定義監控統計策略。

  • Protocol 遠程調用層:封裝 RPC 調用邏輯,擴展接口爲 Protocol, 對應實現有 RegistryProtocol、DubboProtocol、InjvmProtocol 等。

  • Exchange 信息交換層:封裝請求響應模式,同步轉異步,擴展接口 Exchanger,對應擴展實現有 HeaderExchanger 等。

  • Transport 網絡傳輸層:抽象 mina 和 netty 爲統一接口。擴展接口爲 Channel,對應實現有 NettyChannel(默認)、MinaChannel 等;擴展接口Transporter對應的實現類有GrizzlyTransporter、MinaTransporter、NettyTransporter(默認實現);擴展接口Codec2對應實現類有DubboCodec、ThriftCodec等

  • Serialize 數據序列化層:提供能夠複用的一些工具,擴展接口爲 Serialization,對應擴展實現有 DubboSerialization、FastJsonSerialization、Hessian2Serialization、JavaSerialization等,擴展接口ThreadPool對應擴展實現有 FixedThreadPool、CachedThreadPool、LimitedThreadPool 等。

綜上可知Dubbo的分層架構使得Dubbo的每層的功能都是可被替換的,這使得Dubbo的擴展性極強,上面說了那麼多關於擴展點的東西,那麼具體什麼是擴展點呢,下面看下 Dubbo 擴展點一個簡單例子。以擴展點 Protocol 爲例:

@SPI("dubbo")
public interface Protocol {
...
}
複製代碼

擴展點接口的類上面都含有@SPI註解,這裏註解裏面的"dubbo"說明Protocol擴展接口SPI的默認實現是DubboProtocol。

若是咱們想本身寫一個 Protocol 擴展接口的實現類,那麼咱們須要在實現類所在的 Jar 包內的 META-INF/dubbo/ 目錄下建立一個名字爲 org.apache.dubbo.rpc.Protocol 的文本文件,而後配置它的內容爲:

myprotocol=com.alibaba.user.MyProtocol

複製代碼

假設該實現類 MyProtocol 的內容以下:

package com.alibaba.user;
public class MyProtocol implemenets Protocol {
// ...
}

複製代碼

那麼如何使用咱們自定義的擴展實現呢?Dubbo 配置模塊中,擴展點均有對應配置屬性或標籤,以下代碼經過配置標籤方式指定使用哪一個擴展實現:

<dubbo:protocol name="myprotocol" />

複製代碼

注意這裏的 name 必須與 jar 包內 META-INF/dubbo/ 目錄下 org.apache.dubbo.rpc.Protocol 文件中的等號左側的key的名字一致。

3.2 Dubbo內核原理

在Dubbo中框架的可擴展性是靠適配器原理結合加強SPI機制實現的,本書中首先會講解Dubbo的適配器原理,什麼是適配器模式?好比dubbo提供的擴展接口Protocol,Protocol的定義以下:

@SPI("dubbo")
public interface Protocol {
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    ....
}
複製代碼

Dubbo則會使用本書介紹的動態編譯技術爲接口Protocol生成一個適配器類Protocol$Adaptive的對象實例,Dubbo框架中須要使用Protocol的實例的時候實際就是使用的Protocol$Adaptive的對象實例來獲取具體SPI實現類,其代碼以下:

package org.apache.dubbo.rpc;
...
public class Protocol$Adaptive implements Protocol {   
 ...
public Exporter export(Invoker invoker) throws RpcException {
    String string;
    ...
    //(1)
    URL uRL = invoker.getUrl();
    String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
    if (string == null) {
        throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL.toString()).append(") use keys([protocol])").toString());
    }
    //(2)
    Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
    //(3)
    return protocol.export(invoker);
}
    
複製代碼

在dubbo框架中protocol的一個定義爲: private static final Protocol protocol =ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();當調用protocol.export(wrapperInvoker)時候,實際是調用的Protocol$Adaptive的對象實例的export方法,而後後者根據wrapperInvoker中的url裏面的協議類型參數執行代碼(2)使用Dubbo加強SPI方法getExtension獲取對應的SPI實現類,而後調用代碼(3)執行具體SPI實現類的export方法。

而後本書會講解Dubbo加強的SPI機制,本書中首先會藉助 java.sql.Driver擴展接口講解標準JDK中的SPI實現原理以及缺陷,而後講解dubbo的加強SPI如何對其進行改進,如何實現的擴展接口之間自動IOC和擴展接口的功能加強AOP功能。

而後會講解Dubbo使用JavaAssist減小反射調用開銷:Dubbo會給每一個服務提供者的實現類生產一個Wrapper類,這個wrapper類裏面最終調用服務提供者的接口實現類,wrapper類的存在是爲了減小反射的調用。當服務提供方接受到消費方發來的請求後須要根據消費者傳遞過來的方法名和參數反射調用服務提供者的實現類,而反射自己是有性能開銷的,因此dubbo把每一個服務提供者的實現類經過JavaAssist包裝爲一個Wrapper類,那麼Wrapper類爲什麼能減小反射調用那?觀看本書就能夠找到答案

3.3 Dubbo功能實現原理

講解完畢支撐Dubbo框架的內核原理後,本書會先從總體剖析Dubbo服務提供端如何發佈服務的,這包含發佈本地服務和發佈遠程服務的流程, Dubbo服務導出分 本地導出與遠程導出,本地導出使用了 injvm 協議,是一個僞協議,它不開啓端口,不發起遠程調用,只在 JVM 內直接關聯,但執行 Dubbo 的 Filter 鏈;默認下 Dubbo 同時支持本地導出與遠程導出協議,能夠經過ServiceConfig的setScope方式設置,其中配置爲none表示不導出服務,爲remote表示只導出遠程服務,爲local表示只導出本地服務。

你會知道Dubbo如何實現的服務延遲發佈,如何把服務實現類轉換爲 Wrapper 類,以便減小反射的調用,何時構建的dubbo的Filter鏈,都有哪些Wrapper類對擴展接口的實現類進行了功能加強?如何啓動的NettyServer對服務進行監聽,同一個機器上的多個服務提供接口是啓動多個NettyServer仍是一個?如何作到的?如何註冊服務到服務註冊中心的?服務註冊到zookeeper後,其存儲結構是怎麼樣的?

而後本書會講解當服務提供方接受到請求後,如何進行處理的,這包含Filter鏈對請求的處理,以及如何找到對應的被wrapper類包裝後的服務實現類,並對請求進行處理,如何實現的Dubbo的服務提供端異步執行。

而後會講解Dubbo服務消費端的啓動流程,這個過程,你會知道如何基於Proxy SPI擴展實現對服務接口進行代理。與服務提供端同樣,消費端能夠設置是否須要本地服務引用,你會知道在消費端若是沒有指定scope類型,在啓動時候會檢查當前jvm內是否有導出的服務,若是有則自動開啓本地引用(也就是協議類型修改成injvm),則具體調用時候會使用本地暴露的服務來提供服務,而不發起遠程調用。

當具體發起遠程調用時候,你會知道如何動態從服務註冊中心動態訂閱服務信息的,好比訂閱服務提供者地址列表,服務降級信息,服務路由信息,以及Directory目錄與Router路由服務,以及何時構建的路由規則鏈。

如何啓動NettyClient具體發起遠程調用的。而後你會知道同一個服務提供者機器能夠提供多個服務,那麼消費者機器須要與同一個服務提供者機器提供的多個共享鏈接仍是與每一個服務都創建一個?消費端是啓動時候就與服務提供者機器創建好鏈接?

而後會講解具體如何發起一次遠程調用,這個過程你會知道當發起一次rpc調用時候會先通過MockInvoker進行處理,其會看是否設置了 force:return 降級策略,若是設置了則直接返回 mock 值,並不發起遠程調用;否者發起遠程調用,若是遠程調用結果 OK,則直接返回遠程調用返回的結果;若是遠程調用失敗了,則看當前是否設置了 fail:return 的降級策略,若是設置了,則直接返回 mock 值,否者返回調用遠程服務失敗的具體緣由。

若是沒有設置服務降級策略或者mock服務,則會基於SPI機制選擇具體的集羣容錯策略(本文會詳細講解常見的Failover、Failfast、Failsafe、Forking、Broadcast這幾種集羣容錯實現原理,以及講解如何本身基於SPI實現本身的容錯策略),具體集羣容錯策略內有會根據SPI機制選擇設置的服務負載均衡策略(本文會詳細介紹常見的Random、RoundRobin、LeastActive、ConsistentHash),具體負載均衡策略內會基於SPI選擇設置的服務目錄實現,其內部維護了全部服務提供者的服務提供者列表與路由規則,負載均衡策略則會從符合路由規則的地址列表裏面選擇一個invoker返回,而後最終有該invoker執行。若是執行失敗了,則根據具體集羣容錯策略從新選擇一個invoker進行執行....

而後本書會講解Dubbo線程模型與線程池策略,Dubbo 默認的底層網絡通信使用的是 Netty ,服務提供方 NettyServer 使用兩級線程池,其中 EventLoopGroup(boss) 主要用來接受客戶端的連接請求,並把接受的請求分發給 EventLoopGroup(worker) 來處理,boss 和 worker 線程組咱們稱之爲 IO 線程。

若是服務提供方的邏輯能迅速完成,而且不會發起新的 IO 請求,那麼直接在 IO 線程上處理會更快,由於這減小了線程池調度與上下文切換開銷。但若是處理邏輯較慢,或者須要發起新的 IO 請求,好比須要查詢數據庫,則 IO 線程必須派發請求到新的線程池進行處理,不然 IO 線程會被阻塞,將致使不能接收其它請求。

Dubbo中在服務提供端與消費端的IO線程對請求處理時候默認是把請求轉交給dubbo框架的內部線程池來進行處理的,以即可以及時釋放IO線程。

根據IO線程把什麼類型的消息或者請求交給內部線程池來處理,dubbo提供了不一樣的線程模型,本書主要講解Dubbo提供的線程模型AllDispatcher、DirectDispatcher、MessageOnlyDispatcher、ExecutionDispatcher、ConnectionOrderedDispatcher的實現原理,以及線程池策略FixedThreadPool、LimitedThreadPool、EagerThreadPool、CachedThreadPool的實現原理,以及如何基於SPI自定義本身的線程模型與線程池策略。

基礎篇咱們講解到,基於Dubbo APi搭建Dubbo服務時候,服務消費端引入了一個 SDK 二方包,裏面存放着服務提供端提供的全部接口類,泛化接口調用方式主要在服務消費端沒有 API 接口類及模型類元(好比入參和出參的 POJO 類)的狀況下使用。其參數及返回值中沒有對應的 POJO 類,因此全部 POJO 均轉換爲 Map 表示。使用泛化調用時候服務消費模塊再也不須要引入 SDK 二方包,本書會詳細介紹Dubbo中nativejava,true, bean三種泛化調用的實現。

基礎篇咱們講到Dubbo提供了隱式參數傳遞的功能,即服務調用方能夠經過RpcContext.getContext().setAttachment()方法設置附加屬性鍵值對,而後設置的值對能夠在服務提供方服務方法內獲取;本書咱們會詳細介紹如何在在消費端設置參數,而且如何經過網絡把參數傳遞到服務提供方,而後服務提供方如何進行獲取。

正如Dubbo官網所說dubbo從2.7.0版本開始支持全部異步編程接口以CompletableFuture爲基礎,以便解決2.7.0以前版本異步調用的不便與功能缺失。

異步調用實現是基於 NIO 的非阻塞能力實現並行調用,服務消費端不須要啓動多線程便可完成並行調用多個遠程服務,相對多線程開銷較小,以下圖是Dubbo異步調用鏈路概要流程圖圖:

image.png

本書咱們首先講解dubbo服務消費端的異步調用,首先講解2.7.0版本前的異步調用實現原理,咱們會知道future調用get()方法方式實現異步缺點是當業務線程調用get()方法後業務線程會被阻塞,這不是咱們想要的,因此dubbo2.7.0版本提供了在CompletableFuture對象上設置回調函數的方式,讓咱們實現真正的異步調用。

在Provider端非異步執行時候,其對調用方發來的請求的處理是在Dubbo內部線程模型的線程池中的線程來執行的,在dubbo中服務提供方提供的全部的服務接口都是使用這一個線程池來執行的,因此當一個服務執行比較耗時時候,可能會佔用線程池中不少線程,這可能就會致使其餘服務的處理收到影響。

Provider端異步執行則將服務的處理邏輯從Dubbo內部線程池切換到業務自定義線程,避免Dubbo線程池中線程被過分佔用,有助於避免不一樣服務間的互相影響。

可是須要注意provider端異步執行對節省資源和提高RPC響應性能是沒有效果的,這時是由於若是服務處理比較耗時,雖然不是使用Dubbo框架內部線程處理,可是仍是須要業務本身的線程來處理,另外反作用還有會新增一次線程上下文切換(從dubbo內部線程池線程切換到業務線程),模型以下圖11.2.0

image.png

本書首先會講解基於定義CompletableFuture簽名的接口實現異步執行的實現原理,而後講解使用AsyncContext實現異步執行原理,最後講解Dubbo的異步調用與執行引入的新問題以及如何解決的,這包含引入異步調用時候等結果返回後Filter鏈得不到執行的問題,以及異步執行時候上下文參數傳遞問題。

前面章節咱們介紹了服務消費端一次服務調用流程與服務提供端一次服務處理流程,可是仍是有一些東西是咱們沒有提到的,好比服務消費端如何把服務請求信息序列化爲二進制、服務提供方又是如何把消費端發送的二進制數據反序列化爲可識別的POJO對象、好比Dubbo的應用層協議是怎麼樣的。本書咱們就來一一來看dubbo是如何作這些的。

本書會首先講解Dubbo協議,在TCP協議棧中,每層協議都有本身的協議報文格式,好比TCP協議是網絡七層模型中的傳輸層,有TCP協議報文格式;在TCP上層是應用層,應用層協議常見的有http協議等,Dubbo協議做爲創建在TCP協議之上的一種應用層協議,天然也有本身的協議包格式,Dubbo協議也是參考TCP協議棧中的協議,協議內容由header和body兩部分組成,本書會詳細介紹協議header中每一個字段含義。而後講解服務消費方編碼原理,包含當服務消費端發送請求時候,如何把請求內容封裝爲Dubbo協議幀的。而後講解服務提供方接受請求後如何對協議幀進行解碼解決半包粘包問題的。

4、Dubbo-實踐篇

實踐篇咱們來探討如何使用Arthas和一些demo來對研究Dubbo框架實現提供便捷,而且基於Netty與CompletableFuture模擬了RPC同步與純異步調用。

首先本書會介紹如何安裝Arthas,而後講解如何使用Arthas查看查看擴展接口適配器類的源碼,查看服務提供端Wrapper類的源碼,如何查詢Dubbo啓動後都有哪些Filter,而後經過Demo驗證RoundRobin LoadBalance負載均衡原理,而後探討若是根據IP動態路由調用Dubbo服務。

Dubbo的服務消費端基於CompletableFuture實現了功能比較豐富的純異步調用,其實還不僅僅是CompletableFuture的功勞,歸根究竟是Netty的NIO非阻塞功能提供的底層實現,本文咱們就來基於CompletableFuture與Netty來模擬下如何異步發起遠程調用,以及如何使用CompletableFuture自己的功能,讓多個請求的異步結果進行運算,以便加深對dubbo異步調用實現原理的理解。

5、總結

如何你對上面內容感興趣,想深刻研究,可是無從入手,那麼機會來了,專欄

內容包含可是不限於上述內容,你們能夠掃描訂閱該專欄。

相關文章
相關標籤/搜索