天天看點

dubbo源碼分析 之 服務遠端暴露(中)-9 

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

  • dubbo 遠端暴露 – Netty 暴露服務
  • dubbo 遠端暴露 – Zookeeper 連接配接
  • dubbo 遠端暴露 – Zookeeper 注冊 & 訂閱

dubbo 支援以下幾種注冊中心:

dubbo源碼分析 之 服務遠端暴露(中)-9 

官方推薦使用 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實作

dubbo源碼分析 之 服務遠端暴露(中)-9 

不知道大家細心觀察沒有: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 的兩種不同的用戶端的代碼風格統一了起來。

繼續閱讀