1. 程式人生 > >9、dubbo原始碼分析 之 服務遠端暴露(中)

9、dubbo原始碼分析 之 服務遠端暴露(中)

在上一篇文章我們講解了一下 dubbo 遠端服務暴露過程中通過 Netty 進行 Socket 服務暴露。使得遠端客戶端可以訪問這個暴露的服務,這個只是解決了訪問之前點到點的服務呼叫。對於分步式環境當中,越來越多的服務我們如何管理並且治理這些服務是一個問題。因此 dubbo 引入了註冊中心這個概念,把服務暴露、服務呼叫的資訊儲存到註冊中心上面。並且還可以訂閱註冊中心,實現服務自動發現。因為 dubbo 遠端暴露裡面的過程還是比較複雜的,所以我就分為三個文章來講解 dubbo 的遠端暴露:

  • dubbo 遠端暴露 – Netty 暴露服務
  • dubbo 遠端暴露 – Zookeeper 連線
  • dubbo 遠端暴露 – Zookeeper 註冊 & 訂閱

dubbo 支援以下幾種註冊中心

註冊中心成熟度優點問題建議
Zookeeper註冊中心Stable支援基於網路的叢集方式,有廣泛周邊開源產品,建議使用dubbo-2.3.3以上版本(推薦使用)依賴於Zookeeper的穩定性可用於生產環境
Redis註冊中心Stable支援基於客戶端雙寫的叢集方式,效能高要求伺服器時間同步,用於檢查心跳過期髒資料可用於生產環境
Multicast註冊中心Tested去中心化,不需要安裝註冊中心依賴於網路拓普和路由,跨機房有風險小規模應用或開發測試環境
Simple註冊中心TestedDogfooding,註冊中心本身也是一個標準的RPC服務沒有叢集支援,可能單點故障試用

官方推薦使用 zookeeper 為註冊中心。在我們分析一下 dubbo 中是如何整合 zookeeper 之前,我們先來回顧一下 dubbo 服務暴露裡面的主要步驟。

1、RegistryProtocol#export

下面就是 dubbo 暴露的核心步驟的程式碼,可能由於版本的原因(下面的程式碼基於 2.6.1)程式碼會有所差異但是核心思想不變。

    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);

        URL registryUrl = getRegistryUrl(originInvoker);

        //registry provider
        final Registry registry = getRegistry(originInvoker);
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);

        //to judge to delay publish whether or not
        boolean register = registedProviderUrl.getParameter("register", true);

        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {
            register(registryUrl, registedProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }

之前我們分析了第一步:ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); dubbo 基於 socket 的本地暴露提供服務給遠端客戶端呼叫。下面我們就來分析服務遠端暴露的註冊服務到 zookeeper 這個註冊中心上面來實現高可用的。

2、RegistryProtocol#getRegistry

1、把 URL 裡面的 protocol 設定成 <dubbo:registry address="zookeeper://127.0.0.1:2181"裡面設定的 zookeeper。 2、通過 RegistryFactory 的 SPI 介面 RegistryFactory$Adaptive 根據 URL 裡面的 protocol 獲取 zookeeper 的註冊工廠呼叫 getRegistry獲取 zookeeper 註冊中心 -- ZookeeperRegistry。

以下是由 dubbo 位元組碼服務生成的 RegistryFactory$Adaptive

public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
   public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
       if (arg0 == null) throw new IllegalArgumentException("url == null");
       com.alibaba.dubbo.common.URL url = arg0;
       String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
       if (extName == null)
           throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
       com.alibaba.dubbo.registry.RegistryFactory extension = (com.alibaba.dubbo.registry.RegistryFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).getExtension(extName);
       return extension.getRegistry(arg0);
   }
}

3、AbstractRegistryFactory.getRegistry

1、URL 新增 引數interface 為 com.alibaba.dubbo.registry.RegistryService,並去掉 export 引數。 2、ZookeeperRegistryFactory#createRegistry建立 ZookeeperRegistry 例項 3、AbstractRegistry#init()中呼叫loadProperties(),在以下目錄中儲存註冊資訊(以window為例)。

C:\Users\Carl\.dubbo\dubbo-registry-127.0.0.1.cache

4、FailbackRegistry#init()中故障回覆註冊類中建立執行緒池 ScheduledExecutorService 檢測並連線註冊中心,如果失敗就就呼叫 retry()進行重連,高可用。 5、zookeeperTransporter#connect()由於 ZookeeperTransporter 是一個 @SPI 介面並且 @Adaptive,所以會生成一個 ZookeeperTransporter$Adaptive,並且是由RegistryFactory這個 SPI 介面建立的時候通過 SPI 依賴注入建立 ZookeeperRegistryFactory 物件的時候依賴注入的。然後通過ZookeeperRegistryFactory#createRegistry()把這個介面傳入的。而且因為 我的這個版本使用的是 dubbo-2.6.1 版本所以預設使用的是 Curator 這個 Zookeeper 客戶端,之前低版本預設使用的是 Zkclient 這個預設客戶端。如果大家對 dubbo 裡面的 SPI 機制不太瞭解可以看之前的 blog -- 2.dubbo原始碼分析 之 核心SPI實現

這裡寫圖片描述

不知道大家細心觀察沒有:Curator 和 Zkclient 在連線 zookeeper 的時候程式碼風格不太一樣。而 dubbo 在進行 zookeeper 連線的時候通過 ZkClientWrapper這個包裝類使得 Zkclient 與 Curator 的程式碼風格一致。

> 1、Curator -- CuratorZookeeperClient#init()

// 構造連線引數
CuratorFramework client = builder.build();
// 進行連線操作
client.start();

> 2、Zkclient -- ZkclientZookeeperClient#init()

// 構造連線引數
ZkClientWrapper client =  = new ZkClientWrapper(url.getBackupAddress(), 30000);
// 進行連線操作
client.start();

這個就是 dubbo 平等對待第三方框架,而且把 zk 的兩種不同的客戶端的程式碼風格統一了起來。