dubbo源碼導讀二 dubbo服務暴露過程

Dubbo 服務導出過程始於 Spring 容器發佈刷新事件,Dubbo 在接收到事件後,會立即執行服務導出邏輯。整個邏輯大致可分爲三個部分,第一部分是前置工作,主要用於檢查參數,組裝 URL。第二部分是導出服務,包含導出服務到本地 (JVM),和導出服務到遠程兩個過程。第三部分是向註冊中心註冊服務,用於服務發現。本篇文章將會對這三個部分代碼進行詳細的分析。

實現細節概覽

解析服務

基於 dubbo.jar 內的 META-INF/spring.handlers 配置,Spring 在遇到 dubbo 名稱空間時,會回調 DubboNamespaceHandler

所有 dubbo 的標籤,都統一用 DubboBeanDefinitionParser 進行解析,基於一對一屬性映射,將 XML 標籤解析爲 Bean 對象。

在 ServiceConfig.export() 或 ReferenceConfig.get() 初始化時,將 Bean 對象轉換 URL 格式,所有 Bean 屬性轉成 URL 的參數。

然後將 URL 傳給 協議擴展點,基於擴展點的 擴展點自適應機制,根據 URL 的協議頭,進行不同協議的服務暴露或引用。

暴露服務

1. 只暴露服務端口:

在沒有註冊中心,直接暴露提供者的情況下 [1]ServiceConfig 解析出的 URL 的格式爲: dubbo://service-host/com.foo.FooService?version=1.0.0

基於擴展點自適應機制,通過 URL 的 dubbo:// 協議頭識別,直接調用 DubboProtocol的 export() 方法,打開服務端口。

2. 向註冊中心暴露服務:

在有註冊中心,需要註冊提供者地址的情況下 [2]ServiceConfig 解析出的 URL 的格式爲: registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0")

基於擴展點自適應機制,通過 URL 的 registry:// 協議頭識別,就會調用 RegistryProtocol 的 export() 方法,將 export 參數中的提供者 URL,先註冊到註冊中心。

再重新傳給 Protocol 擴展點進行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0,然後基於擴展點自適應機制,通過提供者 URL 的 dubbo:// 協議頭識別,就會調用 DubboProtocol 的 export() 方法,打開服務端口。

服務提供者暴露一個服務的詳細過程

/dev-guide/images/dubbo_rpc_export.jpg

上圖是服務提供者暴露服務的主過程:

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

Dubbo 處理服務暴露的關鍵就在 Invoker 轉換到 Exporter 的過程,上圖中的紅色部分。下面我們以 Dubbo 和 RMI 這兩種典型協議的實現來進行說明:

Dubbo 的實現

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

RMI 的實現

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

滿眼都是 Invoker

由於 Invoker 是 Dubbo 領域模型中非常重要的一個概念,很多設計思路都是向它靠攏。這就使得 Invoker 滲透在整個實現代碼裏,對於剛開始接觸 Dubbo 的人,確實容易給搞混了。 下面我們用一個精簡的圖來說明最重要的兩種 Invoker:服務提供 Invoker 和服務消費 Invoker

/dev-guide/images/dubbo_rpc_invoke.jpg

爲了更好的解釋上面這張圖,我們結合服務消費和提供者的代碼示例來進行說明:

服務消費者代碼:

public class DemoClientAction {
 
    private DemoService demoService;
 
    public void setDemoService(DemoService demoService) {
        this.demoService = demoService;
    }
 
    public void start() {
        String hello = demoService.sayHello("world" + i);
    }
}

上面代碼中的 DemoService 就是上圖中服務消費端的 proxy,用戶代碼通過這個 proxy 調用其對應的 Invoker [5],而該 Invoker 實現了真正的遠程服務調用。

服務提供者代碼:

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

上面這個類會被封裝成爲一個 AbstractProxyInvoker 實例,並新生成一個 Exporter 實例。這樣當網絡通訊層收到一個請求後,會找到對應的 Exporter 實例,並調用它所對應的 AbstractProxyInvoker 實例,從而真正調用了服務提供者的代碼。Dubbo 裏還有一些其他的 Invoker 類,但上面兩種是最重要的。

導出服務入口

服務導出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一個事件響應方法,該方法會在收到 Spring 上下文刷新事件後執行服務導出操作。方法代碼如下:

ServiceBean 是 Dubbo 與 Spring 框架進行整合的關鍵,可以看做是兩個框架之間的橋樑。具有同樣作用的類還有 ReferenceBean。

檢查參數略

加載註冊中心URL列表

Dubbo 允許我們使用不同的協議導出服務,也允許我們向多個註冊中心註冊服務。Dubbo 在 doExportUrls 方法中對多協議,多註冊中心進行了支持。相關代碼如下:

private void doExportUrls() {
    // 加載註冊中心鏈接
    List<URL> registryURLs = loadRegistries(true);
    // 遍歷 protocols,並在每個協議下導出服務
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

組裝provider URL

導出服務到本地

exportLocal()方法內部會把url協議替換爲injvm協議,然後通過自適應擴展機制調用InjvmProtocol的export方法在本地暴露服務。

InjvmProtocol 的 export 方法僅創建了一個 InjvmExporter,無其他邏輯

導出服務到遠程

生成Invoker實例

注意第518行 registryURL上加了一個export參數,參數的值就是providerURL,以便可以從registryURL中獲取providerURL

導出服務

例子中是調用DubboProtocol的export方法暴露服務

跟蹤方法最核心的一步就是創建服務端,開啓監聽端口,默認是使用nettyserver

註冊服務

registryFactory是一個SPI接口的代理類

根據registryURL的協議zookeeper,實際執行ZookeeperRegistryFactory的方法,ZookeeperTransporter也是一個SPI接口。默認是使用curator的客戶端。

通過curator客戶端創建zookeeper目錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章