Dubbo進階(九)- Dubbo 消費者中 代理對象 初始化詳解

上一篇文章中,講到 Dubbo 初始化一個 代理對象時,會執 ReferenceConfiginit 方法,而後執行其

ref = createProxy(map);

而在這一步中,會進行以下幾步:

  1. 判斷是否是 Jvm 類型應用
  2. 組裝url
  3. 代理生成 invoker
  4. 最後 執行 (T) PROXY_FACTORY.getProxy(invoker); 返回type 類型對象。

Dubbo 中 Invoker

InvokerDubbo 的核心模型,代表一個可執行體。在服務提供方,Invoker 用於調用服務提供類。在服務消費方,Invoker 用於執行遠程調用。Invoker 是由 Protocol 實現類構建而來。Protocol 實現類有很多,它本身也是一個 SPI 類型。

上一篇文章中,分析了參數組裝,以及 Wrapper 類 和 SPI 組裝關係。

準備工作

這篇即重點分析 invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
結合上兩篇文章,由於 Protocol 沒有 默認的 Adaptive 類,所以將使用默認的 Protocol$Adaptive 類此處,有需要可以看這篇文章:
Dubbo 中默認的 Adaptive類生成過程及例子
由於 url是 registry 類型,所以最終得到的是 RegistryProtocl,而裏面和Wrapper 類組裝而成的結構爲:
ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper->RegistryProtocol

起始最終 init 中 PROXY_FACTORY 類型爲 :ProtocolFilterWrapper,有前面的分析,包裝類其實是鏈式的組裝結構,即最終會依次調用:
ProtocolFilterWrapperProtocolListenerWrapperQosProtocolWrapperrefer 方法,而由代碼可知,當url 的協議爲 registry 時候,則默認會跳過而執行下一個 Protocolrefer。這樣一來,依次執行玩前三個Wrapper 之後,就會執行 RegistryProtocolrefer
本文將重點分析RegistryProtocol 的 refer,其中會包括Dubbo 集羣容錯的一些相關內容介紹。

RegistryProtocol 的 refer

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // 拼湊 url
        url = URLBuilder.from(url)
                .setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
                .removeParameter(REGISTRY_KEY)
                .build();
        // 獲取註冊中心
        Registry registry = registryFactory.getRegistry(url);
        // 如果類型就是 `RegistryService` 類型
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }

        // group="a,b" or group="*"
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
        String group = qs.get(GROUP_KEY);
        if (group != null && group.length() > 0) {
            if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
                return doRefer(getMergeableCluster(), registry, type, url);
            }
        }
        // 執行 doRefer
        return doRefer(cluster, registry, type, url);
    }

上文代碼中,主要做了以下幾步:

  1. 拼湊url
  2. 判斷是否爲RegistryService,是得化說明不是具體接口類型,所以直接返回
  3. 如果有group 參數的,則使用 MergeableCluster 構建 invoker,否則使用默認的 FailoverCluster 構建。

Registry registry = registryFactory.getRegistry(url); 這是 AbstractRegistryFactory 中方法,是根據url獲取 Registry 過程,Registry 也沒有默認的 Adaptive 類,所以將使用默認的 Adaptive 類,而url在 Registry$Adatpive 被換爲了 zookeeper 協議, 而後會執行 Registry registry = REGISTRIES.get(key); 從緩存中獲取 Regisry,否則會通過url新建一個。

    @Override
    public Registry getRegistry(URL url) {
        // 重新設置path
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(EXPORT_KEY, REFER_KEY)
                .build();
        // 由url獲取key
        String key = url.toServiceStringWithoutResolving();
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
			// 嘗試從緩存中獲取
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            // 通過 SPI 或者 IOC 創建create registry by spi/ioc
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }

以上代碼做了如下幾件事:

  1. 將url中path設置爲 org.apache.dubbo.registry.RegistryService
  2. 嘗試獲取key的緩存,key的格式爲 zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService,這也就說明,不同的接口,可以有不同的註冊中心。
  3. 如果無法找到緩存,則執行 子類具體實現的 createRegistry 新建

ZookeeperRegistyFactory 工廠模式的應用,則是通過 createRegistry 新創鍵一個 ZookeeperRegistry 並返回:

    public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }

可以看下面 ZookeeperRegistry 章節分析。
4. 判斷是否 有配置 group,如果有則使用 MergeableCluster,否則將使用默認的Cluster$AdaptiveFailoverCluster 去調用 子類具體實現的 doRefer 方法
可以看下面 doRefer 小節具體分析

ZookeeperRegistry

那麼 這個 ZookeeperRegistry 做了什麼事呢?

  1. ZookeeperRegistry 構造方法:
    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    // 執行父類構造方法
        super(url);
        // 判斷是否爲 0.0.0.0 的host
        if (url.isAnyHost()) {
            throw new IllegalStateException("registry address == null");
        }
        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(PATH_SEPARATOR)) {
            group = PATH_SEPARATOR + group;
        }
        this.root = group;
        zkClient = zookeeperTransporter.connect(url);
        zkClient.addStateListener(state -> {
            if (state == StateListener.RECONNECTED) {
                try {
                    recover();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        });
    }
  1. FailbackRegistry 構造方法:
    public FailbackRegistry(URL url) {
        super(url);
        this.retryPeriod = url.getParameter(REGISTRY_RETRY_PERIOD_KEY, DEFAULT_REGISTRY_RETRY_PERIOD);

        // since the retry task will not be very much. 128 ticks is enough.
        retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboRegistryRetryTimer", true), retryPeriod, TimeUnit.MILLISECONDS, 128);
    }
  1. AbstractRegistry 構造方法:
    public AbstractRegistry(URL url) {
    // 設置url
        setUrl(url);
        // 嘗試從緩存文件中獲取配置
        // Start file save timer
        syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
        String filename = url.getParameter(FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
        File file = null;
        if (ConfigUtils.isNotEmpty(filename)) {
            file = new File(filename);
            if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
                if (!file.getParentFile().mkdirs()) {
                    throw new IllegalArgumentException("Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
                }
            }
        }
        this.file = file;
        // When starting the subscription center,
        // we need to read the local cache file for future Registry fault tolerance processing.
        // 嘗試從緩存文件中獲取配置
        loadProperties();
        // 告知 機制
        notify(url.getBackupUrls());
    }

以上列舉了 ZookeeperRegistry 的構造方法,以及繼承結構中父類 FailbackRegistryAbstractRegistry 的構造方法。
從後往前講:

  1. 在 AbstractRegistry中,首先是檢查了是否有緩存文件配置,有則讀取出來,文件位置在 ~/.dubbo 下,名字格式爲:
    url.getParameter(FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
    例如以下: /home/anla7856/.dubbo/dubbo-registry-dubbo-consumer-127.0.0.1:2181.cache。 而後則第一次通知了 其他消息監聽的去配置下 url 的 backupUrl,即Zookeeper集羣的其他配置地址。
  2. FailbackRegistryZookeeperRegistry 的父類,裏面主要封裝和記錄了一些失敗的通知或者訂閱信息,在裏面開啓了一個 定時器 HashedWheelTimer,用於失敗重試,而重試周期如果沒有指明,則爲5000ms。
  3. 正題就是 ZookeeperRegistry 中的構造方法,除了調用父類的構造方法,它還負責連接Zookeeper,即內部使用了 一個Dubbo 封裝的 ZookeeperClientZookeeperTransporter 用於操作 Zookeeper。由於可能有多種ZookeeperTransporter 類型,所以事實上它也是 SPI 類型。
    最後,監聽 Zookeeper 的狀態變更消息,從而執行 recover 方法。recover 方法則主要是當需要重連時候,重新嘗試連接以及刷新已獲取的配置。

當獲取完 Registry 後,將其放入 REGISTRIES 中緩存起來,並最終返回該 registry

doRefer

上面的 獲取 registry 方法 是 僅僅是準備工作,即初始化了 相關的 RegistryCluster。而在RegistryProtocol各自的doRefer 方法 則會進行具體一些設定,例如Directory 配置,訂閱配置,註冊監聽器等。

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    	// 創建一個 RegistryDirectory
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        // 設置 Registry 和 protocol
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
        // 從新構造訂閱的URL
        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
        if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
            directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
            registry.register(directory.getRegisteredConsumerUrl());
        }
        directory.buildRouterChain(subscribeUrl);
        directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
                PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));

        Invoker invoker = cluster.join(directory);
        ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
        return invoker;
    }

上面貼出了 RegistryProtocoldoRefer 方法,有以下幾個邏輯點:

  1. 創建一個RegistryDirectory,即服務目錄,服務目錄中存儲了一些和服務提供者有關的信息,通過服務目錄,服務消費者可獲取到服務提供者的信息,比如 ip、端口、服務協議等。通過這些信息,服務消費者就可通過 Netty 等客戶端進行遠程調用。而後設置 RegistryProtocolRegistriesConsumerUrl
  2. 而後,調用 registry.register(directory.getRegisteredConsumerUrl()); 去註冊 這個 registeredConsumerUrl,默認使用 Zookeeper 作爲註冊中心進行服務註冊和管理。由ZookeeperRegistry->FailbackRegistry->AbstractRegistry 依次初始化並調用。最終,執行ZookeeperRegistrydoRegistry語句在zk上面增加一個字節點:
    @Override
    public void doRegister(URL url) {
        try {
            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
        } catch (Throwable e) {
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }
  1. 執行 directory.buildRouterChain(subscribeUrl); 初始化 路由鏈 的初始化,具體路由鏈將由後面文章介紹。
  2. 執行 directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY, PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY)); 去訂閱監聽。
    具體訂閱過程看下一篇詳細分析。
  3. Invoker invoker = cluster.join(directory); 使用默認的 Cluster$Adatpive ,創建一個Invoker 並返回,並且緩存入 ProviderConsumerRegTable 中。

看下 Cluster 的SPI 文件:

mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster
failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster
failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster
failback=org.apache.dubbo.rpc.cluster.support.FailbackCluster
forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster
available=org.apache.dubbo.rpc.cluster.support.AvailableCluster
mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster
broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster
registryaware=org.apache.dubbo.rpc.cluster.support.RegistryAwareCluster

裏面有個 Wrapper 類,所以Cluster 的返回必然是 Wrapper -> Cluster 模式,具體可以看這篇文章分析:
SPI模式中 Wrapper和 SPI 類組裝邏輯

再看看 MockClusterWrapper 類:

public class MockClusterWrapper implements Cluster {

    private Cluster cluster;

    public MockClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory,
                this.cluster.join(directory));
    }

}

裏面join 方法僅僅返回 一個 MockClusterInvoker
所以從代碼中可以推斷出 ,最終在 ReferenceConfig 返回必將是一個 MockClusterInvoker
由打斷點而獲取到的invoker 內容如下所示:
在這裏插入圖片描述
而最終再通過 return (T) PROXY_FACTORY.getProxy(invoker); 返回一個代理類。

所以另一方面,當我們寫代碼時候,直接端點進入,是無法直接進入 type 的 實現類,而是進入MockClusterInvoker

那具體訂閱過程是如何的呢?代理工廠如何生成代理類呢?

這些問題,且看下回分解,感謝閱讀。

覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路:
在這裏插入圖片描述

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