Dubbo 源碼分析 —— 核心流程一覽


我準備戰斗到最后,不是因為我勇敢,是我想見證一切。 ——雙雪濤《獵人》

[TOC]
Thinking

  1. 一個技術,為什么要用它,解決了那些問題?
  2. 如果不用會怎么樣,有沒有其它的解決方法?
  3. 對比其它的解決方案,為什么最終選擇了這種,都有何利弊?
  4. 你覺得項目中還有那些地方可以用到,如果用了會帶來那些問題?
  5. 這些問題你又如何去解決的呢?

本文基于Dubbo 2.6.7-SNAPSHOT

? 本文主要基于《Dubbo 開發指南 —— 框架設計》

1、整體設計

dubbo-framework.jpg

圖例說明:

  • 圖中左邊淡藍背景的為服務消費方使用的接口,右邊淡綠色背景的為服務提供方使用的接口,位于中軸線上的為雙方都用到的接口。
  • 圖中從下至上分為十層,各層均為單向依賴,右邊的黑色箭頭代表層之間的依賴關系,每一層都可以剝離上層被復用,其中,Service 和 Config 層為 API,其它各層均為 SPI。
  • 圖中綠色小塊的為擴展接口,藍色小塊為實現類,圖中只顯示用于關聯各層的實現類。
  • 圖中藍色虛線為初始化過程,即啟動時組裝鏈,紅色實線為方法調用過程,即運行時調時鏈,紫色三角箭頭為繼承,可以把子類看作父類的同一個節點,線上的文字為調用的方法。

1.1 各層說明

  • ========================== Business==============================
  • Service業務層:業務代碼的接口與實現。暴露在外實際的使用Dubbo層。
  • ========================== RPC==============================
  • config 配置層:對外配置接口,以 ServiceConfig, ReferenceConfig 為中心,可以直接初始化配置類,也可以通過 spring 解析配置生成配置類
    • dubbo-config 模塊實現
  • proxy 服務代理層:服務接口透明代理,生成服務的客戶端 Stub 和服務器端 Skeleton, 以 ServiceProxy 為中心,擴展接口為 ProxyFactory
    • dubbo-rpc 模塊實現
  • registry 注冊中心層:封裝服務地址的注冊與發現,以服務 URL 為中心,擴展接口為 RegistryFactory, Registry, RegistryService
    • dubbo-registry 模塊實現
  • cluster 路由層:封裝多個提供者的路由及負載均衡,并橋接注冊中心,以 Invoker 為中心,擴展接口為 Cluster, Directory, Router, LoadBalance
    • dubbo-cluster 模塊實現
  • monitor 監控層:RPC 調用次數和調用時間監控,以 Statistics 為中心,擴展接口為 MonitorFactory, Monitor, MonitorService
    • dubbo-monitor 模塊實現
  • protocol 遠程調用層:封裝 RPC 調用,以 Invocation, Result 為中心,擴展接口為 Protocol, Invoker, Exporter
    • dubbo-rpc 模塊實現
  • ========================== Remoting============================
  • exchange 信息交換層:封裝請求響應模式,同步轉異步,以 Request, Response 為中心,擴展接口為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
    • dubbo-remoting-api 模塊定義接口
  • transport 網絡傳輸層:抽象 mina 和 netty 為統一接口,以 Message 為中心,擴展接口為 Channel, Transporter, Client, Server, Codec
    • dubbo-remoting-api 模塊定義接口
  • serialize 數據序列化層:可復用的一些工具,擴展接口為 Serialization, ObjectInput, ObjectOutput, ThreadPool
    • dubbo-common 模塊實現。

1.2 關系說明

在 RPC 中,Protocol 是核心層,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 調用,然后在 Invoker 的主過程上 Filter 攔截點。

  • dubbo-rpc 模塊可以獨立完成該功能

圖中的 Consumer 和 Provider 是抽象概念,只是想讓看圖者更直觀的了解哪些類分屬于客戶端與服務器端,不用 Client 和 Server 的原因是 Dubbo 在很多場景下都使用 Provider, Consumer, Registry, Monitor 劃分邏輯拓普節點,保持統一概念。

而 Cluster 是外圍概念,所以 Cluster 的目的是將多個 Invoker 偽裝成一個 Invoker,這樣其它人只要關注 Protocol 層 Invoker 即可,加上 Cluster 或者去掉 Cluster 對其它層都不會造成影響,因為只有一個提供者時,是不需要 Cluster 的

  • dubbo-cluster 模塊提供的是非必需的功能,移除不會影響到其它模塊。RPC模塊也可以正常運行。

Proxy 層封裝了所有接口的透明化代理,而在其它層都以 Invoker 為中心,只有到了暴露給用戶使用時,才用 Proxy 將 Invoker 轉成接口,或將接口實現轉成 Invoker,也就是去掉 Proxy 層 RPC 是可以 Run 的,只是不那么透明,不那么看起來像調本地服務一樣調遠程服務。

  • 簡單粗暴的說,Proxy 會攔截 service.doSomething(args) 的調用,“轉發”給該 Service 對應的 Invoker ,從而實現透明化的代理。

而 Remoting 實現是 Dubbo 協議的實現,如果你選擇 RMI 協議,整個 Remoting 都不會用上,Remoting 內部再劃為 Transport 傳輸層和 Exchange 信息交換層,Transport 層只負責單向消息傳輸,是對 Mina, Netty, Grizzly 的抽象,它也可以擴展 UDP 傳輸,而 Exchange 層是在傳輸層之上封裝了 Request-Response 語義。

Registry 和 Monitor 實際上不算一層,而是一個獨立的節點,只是為了全局概覽,用層的方式畫在一起

2、核心流程

2.1 調用鏈

dubbo-extension.jpg
  • 垂直分層如下:
    • 下方 淡藍背景( Consumer ):服務消費方使用的接口
    • 上方 淡綠色背景( Provider ):服務提供方使用的接口
    • 中間 粉色背景( Remoting ):通信部分的接口
  • 自 LoadBalance 向上,每一行分成了多個相同的 Interface ,指的是負載均衡后,向 Provider 發起調用。
  • 左邊 括號 部分,代表了垂直部分更細化的分層,依次是:Common、Remoting、RPC、Interface 。
  • 右邊 藍色虛線( Init ) 為初始化過程,通過對應的組件進行初始化。例如,ProxyFactory 初始化出 Proxy 。

2.2 暴露服務

服務提供方暴露服務藍色初始化鏈(Init)時序圖:


dubbo-export.jpg

2.3.4 步 為本文重點:getInvoker()

2.3 引用服務

消費方引用服務的藍色初始化鏈


dubbo_rpc_refer.jpg

第5步:refer() 第12步 都為本文重點。

3、領域模型

本章分享的位于Dubbo-rpc-api目錄中,如下圖紅框部分:

領域模型

在 Dubbo 的核心領域模型中:

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

3.1 Invoker

com.alibaba.dubbo.rpc

Invoker 是實體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉換成它。

它代表一個可執行體,可向它發起 invoke 調用,它有可能是一個本地的實現,也可能是一個遠程的實現,也可能一個集群實現。

public interface Invoker<T> extends Node {

    /**
     * get service interface.
     *
     * #getInterface() 獲取Service接口
     *
     * @return service interface.
     */
    Class<T> getInterface();

    /**
     * invoke.
     * 調用方法
     *
     * @param invocation
     * @return result
     * @throws RpcException
     */
    Result invoke(Invocation invocation) throws RpcException;

}

3.1.1 詳解Invoker

在dubbo中,萬物皆是Invoker,即便是Exporter也是由Invoker進化而成的

? 由于Invoker在Dubbo領域模型中非常重要的一個概念,很多設計思路都是向它靠攏。

這一思想滲透在整個實現代碼里。

下面簡單的說明Invoker的兩種實現:服務提供的invoker和服務消費的invoker

滿眼都是Invoker

結合Dubbo demo中的 消費和提供者代碼來理解上圖。

  • 服務消費者代碼:
public class DemoClientAction {
    private DemoServer demoServer;

    public void setDemoServer(DemoServer demoServer) {
        this.demoServer = demoServer;
    }

    public void start() {
        String hello = demoServer.sayHello("world");
    }
}
  • 上面代碼中的 DemoService 就是上圖中服務消費端的 Proxy,用戶代碼通過這個 Proxy 調用其對應的 Invoker,而該 Invoker 實現了真正的遠程服務調用。
  • 服務提供者代碼:
public class DemoServiceImpl implements DemoService {

    public String sayHello(String name) throws RemoteException {
        return "Hello " + name;
    }
}
  • 上面這個類會被封裝成為一個 AbstractProxyInvoker 實例,并新生成一個 Exporter 實例。這樣當網絡通訊層收到一個請求后,會找到對應的 Exporter 實例,并調用它所對應的 AbstractProxyInvoker 實例,從而真正調用了服務提供者的代碼。

3.1.2Invoker 類圖

? 上文所提到的,在Dubbo中invoker是一個非常重要的概念,既可以理解為萬物皆為Invoker。所以在Dubbo中的實現類非常多。

Invoker類圖

3.2 Invocation

com.alibaba.dubbo.rpc.Invocation

Invocation 是會話域,它持有調用過程中的變量,比如方法名,參數等。

public interface Invocation {

    /**
     * get method name.
     *
     * 獲取方法名
     * @return method name.
     * @serial
     */
    String getMethodName();

    /**
     * get parameter types.
     *
     * 獲取方法參數類型數組
     * @return parameter types.
     * @serial
     */
    Class<?>[] getParameterTypes();

    /**
     * get arguments.
     *
     * 獲取方法參數數組
     * @return arguments.
     * @serial
     */
    Object[] getArguments();

    /**
     * get attachments.
     *
     * 獲取隱式參數相關
     * @return attachments.
     * @serial
     */
    Map<String, String> getAttachments();

    /**
     * get attachment by key.
     *
     * @return attachment value.
     * @serial
     */
    String getAttachment(String key);

    /**
     * get attachment by key with default value.
     *
     * @return attachment value.
     * @serial
     */
    String getAttachment(String key, String defaultValue);

    /**
     * get the invoker in current context.
     *
     * 在當前上下文中獲取調用者 invoker
     *
     * 獲取對應的invoker對象
     *
     * @return invoker.
     * @transient
     */
    Invoker<?> getInvoker();

}

3.2.1 類圖

Invocation類圖
  • DecodeableRpcInvocation:是Dubbo協議獨有的。

3.3 Result

com.alibaba.dubbo.rpc.Result

Result 是會話域,它持有調用過程中返回值,異常等。

? RPC調用的結果集

public interface Result {

    /**
     * Get invoke result.
     * <p>
     * 獲取返回值
     *
     * @return result. if no result return null.
     */
    Object getValue();

    /**
     * Get exception.
     * <p>
     * 獲取返回的異常
     *
     * @return exception. if no exception return null.
     */
    Throwable getException();

    /**
     * Has exception.
     * <p>
     * 判斷是否存在異常
     *
     * @return has exception.
     */
    boolean hasException();

    /**
     * Recreate.
     * <p>
     * <code>
     * if (hasException()) {
     * throw getException();
     * } else {
     * return getValue();
     * }
     * </code>
     * <p>
     * com.alibaba.dubbo.rpc.RpcResult
     * RpcResult 中針對recreate() 的實現。
     *
     * @return result.
     * @throws if has exception throw it.
     * @see RpcResult // 具體實現
     */
    Object recreate() throws Throwable;

    /**
     * @see com.alibaba.dubbo.rpc.Result#getValue()
     * @deprecated Replace to getValue()
     */
    @Deprecated
    Object getResult();

    /**
     * 下面的getAttachments等方法 都是獲取但會的隱式參數相關。
     */

    /**
     * get attachments.
     *
     * @return attachments.
     */
    Map<String, String> getAttachments();

    /**
     * get attachment by key.
     *
     * @return attachment value.
     */
    String getAttachment(String key);

    /**
     * get attachment by key with default value.
     *
     * @return attachment value.
     */
    String getAttachment(String key, String defaultValue);

}

#recreate()的具體實現:

    private Object result;

    private Throwable exception;  

@Override
public Object recreate() throws Throwable {
        if (exception != null) {
            throw exception;
        }
        return result;
    }

3.3.1 類圖

Result類圖

3.4 Filter

com.alibaba.dubbo.rpc.Filter

過濾器接口,和我們平時理解的 javax.servlet.Filter 基本一致。

/**
 * Filter. (SPI, Singleton, ThreadSafe)
 */
@SPI
public interface Filter {

    /**
     * do invoke filter.
     執行invoker的過濾邏輯。
     * <p>
     * <code>
     * // before filter 自己實現
     * Result result = invoker.invoke(invocation);
     * // after filter 自己實現
     * return result;
     * </code>
     *
     * @param invoker    service
     * @param invocation invocation.
     * @return invoke result.
     * @throws RpcException
     * @see com.alibaba.dubbo.rpc.Invoker#invoke(Invocation)
     */
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;

}

3.4.1 類圖

Filter類圖

4.5 ProxyFactory

com.alibaba.dubbo.rpc.ProxyFactory代理工廠類

/**
 * ProxyFactory. (API/SPI, Singleton, ThreadSafe)
 */
@SPI("javassist")
public interface ProxyFactory {

    /**
     * create proxy.
     *
     * 創建Proxy,在引用服務調用。
     *
     * @param invoker
     * @return proxy
     */
    @Adaptive({Constants.PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker) throws RpcException;

    /**
     * create proxy.
     *
     * @param invoker
     * @return proxy
     */
    @Adaptive({Constants.PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;

    /**
     * create invoker.
     *
     * 創建Invoker,在暴露服務時調用。
     *
     * @param <T>
     * @param proxy Service對象
     * @param type  Service接口類型
     * @param url   Service對應的Dubbo URL
     * @return invoker
     */
    @Adaptive({Constants.PROXY_KEY})
    <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}

服務消費者消費一個服務的詳細過程

dubbo_rpc_refer.jpg

首先 ReferenceConfig 類的 init 方法調用 Protocolrefer 方法生成 Invoker 實例(如上圖中的紅色部分),這是服務消費的關鍵。接下來把 Invoker 轉換為客戶端需要的接口(如:HelloWorld)。

  • 從圖中我們可以看出,方法的 invoker 參數,通過 Protocol 將 Service接口 創建出 Invoker 。
  • 通過創建 Service 的 Proxy ,實現我們在業務代理調用 Service 的方法時,透明的內部轉換成調用 Invoker 的 #invoke(Invocation) 方法
  • 服務提供者暴露服務的 主過程 如下圖:

首先 ServiceConfig 類拿到對外提供服務的實際類 ref(如:HelloWorldImpl),然后通過 ProxyFactory 類的 getInvoker 方法使用 ref 生成一個 AbstractProxyInvoker 實例,到這一步就完成具體服務到 Invoker 的轉化。接下來就是 Invoker 轉換到 Exporter 的過程。

從圖中我們可以看出,該方法創建的 Invoker ,下一步會提交給 Protocol ,從 Invoker 轉換到 Exporter 。

Dubbo 的實現

Dubbo 協議的 Invoker 轉為 Exporter 發生在 DubboProtocol 類的 export 方法,它主要是打開 socket 偵聽服務,并接收客戶端發來的各種請求,通訊細節由 Dubbo 自己實現。

RMI 的實現

RMI 協議的 Invoker 轉為 Exporter 發生在 RmiProtocol類的 export 方法,它通過 Spring 或 Dubbo 或 JDK 來實現 RMI 服務,通訊細節這一塊由 JDK 底層來實現,這就省了不少工作量。

3.5.1 類圖

image-20200205163900241.png

從類圖可以看出,Dubbo支持JavassistJDK Proxy兩種方式生成代理。

3.6 Protocol

com.alibaba.dubbo.rpc.Protocol Dubbo支持協議的頂層接口

Protocol 是服務域,它是 Invoker 暴露和引用的主功能入口。
它負責 Invoker 的生命周期管理。

/**
 * Protocol. (API/SPI, Singleton, ThreadSafe)
 *
 * Dubbo 支持RPC協議的 頂層接口
 */
@SPI("dubbo")
public interface Protocol {

    /**
     * Get default port when user doesn't config the port.
     *
     * 定義 默認端口
     *
     * @return default port
     */
    int getDefaultPort();

    /**
     * Export service for remote invocation: <br>
     * 1. Protocol should record request source address after receive a request:
     * RpcContext.getContext().setRemoteAddress();<br>
     * 2. export() must be idempotent, that is, there's no difference between invoking once and invoking twice when
     * export the same URL<br>
     * 3. Invoker instance is passed in by the framework, protocol needs not to care <br>
     *
     * 暴露遠程調用服務:
     * 1. 協議在接受請求時,應該記錄請求的來源地址:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必須是冪等的,也就是說,在暴露服務時,一次調用和兩次調用時沒有區別的,
     * 3.傳入的Invoker實例由框架實現并傳入,協議無需關心。
     *
     * @param <T>     Service type 服務的類型
     * @param invoker Service invoker 服務的執行體
     * @return exporter reference for exported service, useful for unexport the service later
     * @throws RpcException thrown when error occurs during export the service, for example: port is occupied
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * Refer a remote service: <br> 引用遠程服務
     * 1. When user calls `invoke()` method of `Invoker` object which's returned from `refer()` call, the protocol
     * needs to correspondingly execute `invoke()` method of `Invoker` object <br>
     * 2. It's protocol's responsibility to implement `Invoker` which's returned from `refer()`. Generally speaking,
     * protocol sends remote request in the `Invoker` implementation. <br>
     * 3. When there's check=false set in URL, the implementation must not throw exception but try to recover when
     * connection fails.
     *
     * 引用遠程服務:
     * 1. 當用戶調用Refer()所返回的Invoker對象的invoke()方法時,協議需要相應地執行Invoker對象的Invoke()方法
     * 2. 實現由`refer()`返回的`Invoker`是協議的責任。一般來說,協議在`Invoker`實現中發送遠程請求。
     * 3. 當url中設置了 check = false時,連接失敗時不能拋出異常,且只能內部消化。
     *
     * @param <T>  Service type 服務的類型
     * @param type Service class 服務的 class對象
     * @param url  URL address for the remote service 遠程服務的url地址
     * @return invoker service's local proxy 服務的本地代理
     * @throws RpcException when there's any error while connecting to the service provider 當連接服務提供方失敗時,拋出該異常。
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    /**
     * Destroy protocol: <br>
     * 1. Cancel all services this protocol exports and refers <br>
     * 2. Release all occupied resources, for example: connection, port, etc. <br>
     * 3. Protocol can continue to export and refer new service even after it's destroyed.
     *
     * 銷毀/釋放協議:
     * 1. 取消該協議所有已經暴露和引用的服務。<br>
     * 2. 釋放協議所占用的所有資源,比如:連接,端口等等。。
     * 3. 協議即使銷毀后也可以繼續暴露并引用新服務。
     */
    void destroy();

}

3.6.1 類圖

Protocl類圖

從類圖就可以很清楚的看出,Dubbo支持多協議。

3.7 Exporter

com.alibaba.dubbo.rpc.Exporter

Exporter,Invoker 暴露服務在Protocol上的對象。

/**
 * Exporter. (API/SPI, Prototype, ThreadSafe)
 *
 * 暴露服務的 頂層接口
 *
 * @see com.alibaba.dubbo.rpc.Protocol#export(Invoker)
 * @see com.alibaba.dubbo.rpc.ExporterListener
 * @see com.alibaba.dubbo.rpc.protocol.AbstractExporter
 */
public interface Exporter<T> {

    /**
     * get invoker.
     *
     * 獲取對應的Invoker
     *
     * @return invoker
     */
    Invoker<T> getInvoker();

    /**
     * unexport.
     *
     * 取消 暴露
     * <p>
     * <code>
     * getInvoker().destroy();
     * </code>
     */
    void unexport();

}

3.7.1 類圖


類圖

3.8 ExporterListener

com.alibaba.dubbo.rpc.ExporterListener

Exporter 監聽器

/**
 * ExporterListener. (SPI, Singleton, ThreadSafe)
 */
@SPI
public interface ExporterListener {

    /**
     * The exporter exported.
     *
     * 當服務暴露完成
     *
     * @param exporter
     * @throws RpcException
     * @see com.alibaba.dubbo.rpc.Protocol#export(Invoker)
     */
    void exported(Exporter<?> exporter) throws RpcException;

    /**
     * The exporter unexported.
     *
     * 當服務取消暴露完成
     *
     * @param exporter
     * @throws RpcException
     * @see com.alibaba.dubbo.rpc.Exporter#unexport()
     */
    void unexported(Exporter<?> exporter);

}

3.8.1 類圖

3.9 InvokerListener

com.alibaba.dubbo.rpc.InvokerListener invoker監聽器

/**
 * InvokerListener. (SPI, Singleton, ThreadSafe)
 * Invoker 監聽器
 */
@SPI
public interface InvokerListener {

    /**
     * The invoker referred
     *
     * 當服務引用完成
     *
     * @param invoker
     * @throws RpcException
     * @see com.alibaba.dubbo.rpc.Protocol#refer(Class, com.alibaba.dubbo.common.URL)
     */
    void referred(Invoker<?> invoker) throws RpcException;

    /**
     * The invoker destroyed.
     *
     * 當服務銷毀引用完成
     *
     * @param invoker
     * @see com.alibaba.dubbo.rpc.Invoker#destroy()
     */
    void destroyed(Invoker<?> invoker);

}

3.9.1 類圖

4、總結

? 本文重點概述了,Dubbo在服務的暴露與引用的大概流程。

本文僅供筆者本人學習,一起進步!

——努力努力再努力xLg

加油!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容