我準備戰斗到最后,不是因為我勇敢,是我想見證一切。 ——雙雪濤《獵人》
[TOC]
Thinking
- 一個技術,為什么要用它,解決了那些問題?
- 如果不用會怎么樣,有沒有其它的解決方法?
- 對比其它的解決方案,為什么最終選擇了這種,都有何利弊?
- 你覺得項目中還有那些地方可以用到,如果用了會帶來那些問題?
- 這些問題你又如何去解決的呢?
本文基于Dubbo 2.6.7-SNAPSHOT
? 本文主要基于《Dubbo 開發指南 —— 框架設計》
1、整體設計
圖例說明:
- 圖中左邊淡藍背景的為服務消費方使用的接口,右邊淡綠色背景的為服務提供方使用的接口,位于中軸線上的為雙方都用到的接口。
- 圖中從下至上分為十層,各層均為單向依賴,右邊的黑色箭頭代表層之間的依賴關系,每一層都可以剝離上層被復用,其中,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 調用鏈
- 垂直分層如下:
- 下方 淡藍背景( Consumer ):服務消費方使用的接口
- 上方 淡綠色背景( Provider ):服務提供方使用的接口
- 中間 粉色背景( Remoting ):通信部分的接口
- 自 LoadBalance 向上,每一行分成了多個相同的 Interface ,指的是負載均衡后,向 Provider 發起調用。
- 左邊 括號 部分,代表了垂直部分更細化的分層,依次是:Common、Remoting、RPC、Interface 。
- 右邊 藍色虛線( Init ) 為初始化過程,通過對應的組件進行初始化。例如,ProxyFactory 初始化出 Proxy 。
2.2 暴露服務
服務提供方暴露服務藍色初始化鏈(Init)時序圖:
2.3.4 步 為本文重點:getInvoker()
2.3 引用服務
消費方引用服務的藍色初始化鏈
第5步:refer() 第12步 都為本文重點。
3、領域模型
本章分享的位于Dubbo-rpc-api
目錄中,如下圖紅框部分:
在 Dubbo 的核心領域模型中:
- Protocol 是服務域,它是 Invoker 暴露和引用的主功能入口,它負責 Invoker 的生命周期管理。
- Invoker 是實體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉換成它,它代表一個可執行體,可向它發起 invoke 調用,它有可能是一個本地的實現,也可能是一個遠程的實現,也可能一個集群實現。
- Invocation 是會話域,它持有調用過程中的變量,比如方法名,參數等。
3.1 Invoker
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
結合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中的實現類非常多。
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 類圖
- DecodeableRpcInvocation:是Dubbo協議獨有的。
3.3 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 類圖
3.4 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 類圖
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;
}
服務消費者消費一個服務的詳細過程
首先
ReferenceConfig
類的 init
方法調用 Protocol
的 refer
方法生成 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 類圖
從類圖可以看出,Dubbo支持Javassist
和JDK 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 類圖
從類圖就可以很清楚的看出,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
加油!