dubbo--5服務引用

曲終人散121發表於2020-12-02

Dubbo 服務引用的時機有兩個:

1在 Spring 容器呼叫 ReferenceBean 的 afterPropertiesSet 方法時引用服務

2 ReferenceBean 對應的服務被注入到其他類中時引用。

這兩個引用服務的時機區別在於,第一個是餓漢式的,第二個是懶漢式的。預設情況下,Dubbo 使用懶漢式引用服務。如果需要使用餓漢式,可通過配置 dubbo:reference 的 init 屬性開啟

按照 Dubbo 預設配置進行分析

官方時序

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-UD69Bx5J-1606840672341)(5%20%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8.assets/dubbo-export.jpg)]

0 入口

整個分析過程從 ReferenceBean 的 getObject 方法開始。當我們的服務被注入到其他類中時,Spring 會第一時間呼叫 getObject 方法,並由該方法執行服務引用邏輯。按照慣例,在進行具體工作之前,需先進行配置檢查與收集工作。接著根據收集到的資訊決定服務用的方式,有三種

1是引用本地 (JVM) 服務

2通過直連方式引用遠端服務

3通過註冊中心引用遠端服務

不管是哪種引用方式,最後都會得到一個 Invoker 例項。如果有多個註冊中心,多個服務提供者,這個時候會得到一組 Invoker 例項,此時需要通過叢集管理類 Cluster 將多個 Invoker 合併成一個例項。合併後的 Invoker 例項已經具備呼叫本地或遠端服務的能力了,但並不能將此例項暴露給使用者使用,這會對使用者業務程式碼造成侵入。此時框架還需要通過代理工廠類 (ProxyFactory) 為服務介面生成代理類,並讓代理類去呼叫 Invoker 邏輯。避免了 Dubbo 框架程式碼對業務程式碼的侵入,同時也讓框架更容易使用。

該方法定義在 Spring 的 FactoryBean 介面中,ReferenceBean 實現了這個方法。實現程式碼如下:

public Object getObject() throws Exception {
    return get();
}

public synchronized T get() {
    if (destroyed) {
        throw new IllegalStateException("Already destroyed!");
    }
    // 檢測 ref 是否為空,為空則通過 init 方法建立
    if (ref == null) {
        // init 方法主要用於處理配置,以及呼叫 createProxy 生成代理類
        init();
    }
    return ref;
}

1.配置檢查

配置解析邏輯封裝在 ReferenceConfig 的 init 方法中,下面進行分析。

2 引用服務

主邏輯

private T createProxy(Map<String, String> map) {
    URL tmpUrl = new URL("temp", "localhost", 0, map);
    final boolean isJvmRefer;
    if (isInjvm() == null) {
        // url 配置被指定,則不做本地引用
        if (url != null && url.length() > 0) {
            isJvmRefer = false;
        // 根據 url 的協議、scope 以及 injvm 等引數檢測是否需要本地引用
        // 比如如果使用者顯式配置了 scope=local,此時 isInjvmRefer 返回 true
        } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
            isJvmRefer = true;
        } else {
            isJvmRefer = false;
        }
    } else {
        // 獲取 injvm 配置值
        isJvmRefer = isInjvm().booleanValue();
    }

    // 本地引用
    if (isJvmRefer) {
        // 生成本地引用 URL,協議為 injvm
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
        // 呼叫 refer 方法構建 InjvmInvoker 例項
        invoker = refprotocol.refer(interfaceClass, url);
        
    // 遠端引用
    } else {
        // url 不為空,表明使用者可能想進行點對點呼叫
        if (url != null && url.length() > 0) {
            // 當需要配置多個 url 時,可用分號進行分割,這裡會進行切分
            String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
            if (us != null && us.length > 0) {
                for (String u : us) {
                    URL url = URL.valueOf(u);
                    if (url.getPath() == null || url.getPath().length() == 0) {
                        // 設定介面全限定名為 url 路徑
                        url = url.setPath(interfaceName);
                    }
                    
                    // 檢測 url 協議是否為 registry,若是,表明使用者想使用指定的註冊中心
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        // 將 map 轉換為查詢字串,並作為 refer 引數的值新增到 url 中
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
                        // 合併 url,移除服務提供者的一些配置(這些配置來源於使用者配置的 url 屬性),
                        // 比如執行緒池相關配置。並保留服務提供者的部分配置,比如版本,group,時間戳等
                        // 最後將合併後的配置設定為 url 查詢字串中。
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        } else {
            // 載入註冊中心 url
            List<URL> us = loadRegistries(false);
            if (us != null && !us.isEmpty()) {
                for (URL u : us) {
                    URL monitorUrl = loadMonitor(u);
                    if (monitorUrl != null) {
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
                    // 新增 refer 引數到 url 中,並將 url 新增到 urls 中
                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                }
            }

            // 未配置註冊中心,丟擲異常
            if (urls.isEmpty()) {
                throw new IllegalStateException("No such any registry to reference...");
            }
        }

        // 單個註冊中心或服務提供者(服務直連,下同)
        if (urls.size() == 1) {
            // 呼叫 RegistryProtocol 的 refer 構建 Invoker 例項
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
            
        // 多個註冊中心或多個服務提供者,或者兩者混合
        } else {
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;

            // 獲取所有的 Invoker
            for (URL url : urls) {
                // 通過 refprotocol 呼叫 refer 構建 Invoker,refprotocol 會在執行時
                // 根據 url 協議頭載入指定的 Protocol 例項,並呼叫例項的 refer 方法
                invokers.add(refprotocol.refer(interfaceClass, url));
                if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                    registryURL = url;
                }
            }
            if (registryURL != null) {
                // 如果註冊中心連結不為空,則將使用 AvailableCluster
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                // 建立 StaticDirectory 例項,並由 Cluster 對多個 Invoker 進行合併
                invoker = cluster.join(new StaticDirectory(u, invokers));
            } else {
                invoker = cluster.join(new StaticDirectory(invokers));
            }
        }
    }

    Boolean c = check;
    if (c == null && consumer != null) {
        c = consumer.isCheck();
    }
    if (c == null) {
        c = true;
    }
    
    // invoker 可用性檢查
    if (c && !invoker.isAvailable()) {
        throw new IllegalStateException("No provider available for the service...");
    }

    // 生成代理類
    return (T) proxyFactory.getProxy(invoker);
}

首先根據配置檢查是否為本地呼叫,若是,則呼叫 InjvmProtocol 的 refer 方法生成 InjvmInvoker 例項。若不是,則讀取直連配置項,或註冊中心 url,並將讀取到的 url 儲存到 urls 中。然後根據 urls 元素數量進行後續操作。若 urls 元素數量為1,則直接通過 Protocol 自適應擴充類構建 Invoker 例項介面。若 urls 元素數量大於1,即存在多個註冊中心或服務直連 url,此時先根據 url 構建 Invoker。然後再通過 Cluster 合併多個 Invoker,最後呼叫 ProxyFactory 生成代理類。Invoker 的構建過程以及代理類的過程比較重要

2.1建立 Invoker

Invoker 是 Dubbo 的核心模型,代表一個可執行體。在服務提供方,Invoker 用於呼叫服務提供類。在服務消費方,Invoker 用於執行遠端呼叫。Invoker 是由 Protocol 實現類構建而來。Protocol 實現類有很多,本節會分析最常用的兩個,分別是 RegistryProtocol 和 DubboProtocol

DubboProtocol 的 refer 方法原始碼。如下:

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    optimizeSerialization(url);
    // 建立 DubboInvoker
    DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    invokers.add(invoker);
    return invoker;
}

上面方法看起來比較簡單,不過這裡有一個呼叫需要我們注意一下,即 getClients。這個方法用於獲取客戶端例項,例項型別為 ExchangeClient。ExchangeClient 實際上並不具備通訊能力,它需要基於更底層的客戶端例項進行通訊。比如 NettyClient、MinaClient 等,預設情況下,Dubbo 使用 NettyClient 進行通訊。接下來,我們簡單看一下 getClients 方法的邏輯

整體時序

ReferenceBean.getObject()
  -->ReferenceConfig.get()
    -->init()
      -->createProxy(map)
        -->refprotocol.refer(interfaceClass, urls.get(0))
          -->ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("registry");
          -->extension.refer(arg0, arg1);
            -->ProtocolFilterWrapper.refer
              -->RegistryProtocol.refer
                -->registryFactory.getRegistry(url)//建立zk的連線,和服務端釋出一樣(省略程式碼)
                -->doRefer(cluster, registry, type, url)
                  -->registry.register//建立zk的節點,和服務端釋出一樣(省略程式碼)。節點名為:dubbo/com.alibaba.dubbo.demo.DemoService/consumers
                  -->registry.subscribe//訂閱zk的節點,和服務端釋出一樣(省略程式碼)。   /dubbo/com.alibaba.dubbo.demo.DemoService/providers, 
                                                                        /dubbo/com.alibaba.dubbo.demo.DemoService/configurators,
                                                                         /dubbo/com.alibaba.dubbo.demo.DemoService/routers]
                    -->notify(url, listener, urls);
                      -->FailbackRegistry.notify
                        -->doNotify(url, listener, urls);
                          -->AbstractRegistry.notify
                            -->saveProperties(url);//把服務端的註冊url資訊更新到C:\Users\bobo\.dubbo\dubbo-registry-192.168.48.117.cache
	                          -->registryCacheExecutor.execute(new SaveProperties(version));//採用執行緒池來處理
	                        -->listener.notify(categoryList)
	                          -->RegistryDirectory.notify
	                            -->refreshInvoker(invokerUrls)//重新整理快取中的invoker列表 consumer特有的!!!!
	                              -->destroyUnusedInvokers(oldUrlInvokerMap,newUrlInvokerMap); // 關閉未使用的Invoker
	                              -->最終目的:重新整理Map<String, Invoker<T>> urlInvokerMap 物件
	                                                                                                                       重新整理Map<String, List<Invoker<T>>> methodInvokerMap物件
                  -->cluster.join(directory)//加入叢集路由
                    -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension("failover");
                      -->MockClusterWrapper.join
                        -->this.cluster.join(directory)
                          -->FailoverCluster.join
                            -->return new FailoverClusterInvoker<T>(directory)
                            -->new MockClusterInvoker
        -->proxyFactory.getProxy(invoker)//建立服務代理
          -->ProxyFactory$Adpative.getProxy
            -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
              -->StubProxyFactoryWrapper.getProxy
                -->proxyFactory.getProxy(invoker)
                  -->AbstractProxyFactory.getProxy
                    -->getProxy(invoker, interfaces)
                      -->Proxy.getProxy(interfaces)//目前代理物件interface com.alibaba.dubbo.demo.DemoService, interface com.alibaba.dubbo.rpc.service.EchoService
                      -->InvokerInvocationHandler// 採用jdk自帶的InvocationHandler,建立InvokerInvocationHandler物件。
	                          
	                          
                    

相關文章