Dubbo系列筆記之服務引用過程,不服不行 一、引言 二、服務引用的起點 三、配置檢查處理 四、引用服務 五、創建 Invoker 1. DubboProtocol

寫在前面:2020年面試必備的Java後端進階面試題總結了一份複習指南在Github上,內容詳細,圖文並茂,有需要學習的朋友可以Star一下!
GitHub地址:https://github.com/abel-max/Java-Study-Note/tree/master

一、引言

服務引用有 直連註冊中心 兩種方式,一般來說直連方式不推薦用於生產,僅提供測試或預發佈的調試使用。所以本篇重點分析通過註冊中心引用服務的過程。

二、服務引用的起點

Dubbo 服務引用的起點有兩個,一般來說我們都是以 ReferenceBean 對應的服務注入形式使用,例如常用的註解形式 @DubboReference ( @Reference 註解在新版 Dubbo 中已廢棄);另一種是 Spring 容器調用 ReferenceBean 的 #afterPropertiesSet 方法時引用服務,這種方式需要配置 <dubbo:reference> 的 init 屬性開啓,Dubbo 默認使用第一種方式。

我們從 ReferenceBean 入手:

ReferenceBean 實現了 FactoryBean 和 InitializingBean :

  • InitializingBean
    看一下 #afterPropertiesSet 方法:
@Override
 @SuppressWarnings({"unchecked"})
 public void afterPropertiesSet() throws Exception {     // 初始化 Dubbo config bean
     prepareDubboConfigBeans();
     // lazy init by default.
     if (init == null) {
         init = false;
     }
     // eager init if necessary.
     if (shouldInit()) {
         getObject();
     }
  }
  • FactoryBean
    Spring 通過調用 #getBean 方法可以返回 bean 的實例,而實現了 FactoryBean 接口,會調用 #getObject 方法返回 bean 的實例。
@Override
    public Object getObject() {
    // 調用 ReferenceConfig 的 get 方法獲取 bean 實例
        return get();
    }

比較上面兩個方法,我們可以知道 Spring 在實例化 Dubbo 的 ReferenceBean 時會調用 ReferenceConfig 的 #get 方法獲取 bean 實例,執行 Dubbo 服務引用的過程。

三、配置檢查處理

繼續跟隨 ReferenceConfig 的 #get 方法:

public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
        }        // 若服務引用代理爲空,執行 init 方法
        if (ref == null) {
            // 處理配置,調用 createProxy 生成代理類
            init();
        }
        return ref;
    }
  • ReferenceConfig 的 init( ) 方法
public synchronized void init() {
      // 標識是否已經初始化,避免重複初始化
      if (initialized) {
          return;
      }      // 獲取 DubboBootstrap 引導類實例      if (bootstrap == null) {
          bootstrap = DubboBootstrap.getInstance();          bootstrap.init();      }      // 檢查接口、consumer 等配置是否合法,並對相應的配置賦值      checkAndUpdateSubConfigs();      // 本地存根檢查
      checkStubAndLocal(interfaceClass);      ConfigValidationUtils.checkMock(interfaceClass, this);      Map<String, String> map = new HashMap<String, String>();
      map.put(SIDE_KEY, CONSUMER_SIDE);      // 加入運行時參數,Dubbo 版本號、時間戳、進程號等
      ReferenceConfigBase.appendRuntimeParameters(map);
      // 是否爲泛化接口
      if (!ProtocolUtils.isGeneric(generic)) {
          String revision = Version.getVersion(interfaceClass, version);          if (revision != null && revision.length() > 0) {
              map.put(REVISION_KEY, revision);          }          // 獲取接口的方法列表,加入 map
          String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();          if (methods.length == 0) {
              logger.warn("No method found in service interface " + interfaceClass.getName());
              map.put(METHODS_KEY, ANY_VALUE);          } else {
              map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));          }      }      map.put(INTERFACE_KEY, interfaceName);      // 將 ApplicationConfig、ConsumerConfig、ReferenceConfig 等對象的字段信息添加到 map 中
      AbstractConfig.appendParameters(map, getMetrics());
      AbstractConfig.appendParameters(map, getApplication());
      AbstractConfig.appendParameters(map, getModule());
      AbstractConfig.appendParameters(map, consumer);
      AbstractConfig.appendParameters(map, this);
      Map<String, AsyncMethodInfo> attributes = null;      if (CollectionUtils.isNotEmpty(getMethods())) {
          attributes = new HashMap<>();          for (MethodConfig methodConfig : getMethods()) {
              AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName());
              String retryKey = methodConfig.getName() + ".retry";
              if (map.containsKey(retryKey)) {
                  String retryValue = map.remove(retryKey);                  if ("false".equals(retryValue)) {
                      map.put(methodConfig.getName() + ".retries", "0");
                  }              }              AsyncMethodInfo asyncMethodInfo = AbstractConfig.convertMethodConfig2AsyncInfo(methodConfig);              if (asyncMethodInfo != null) {
                  attributes.put(methodConfig.getName(), asyncMethodInfo);              }          }      }      // 從系統變量中獲取服務消費者 ip      String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);      if (StringUtils.isEmpty(hostToRegistry)) {
          hostToRegistry = NetUtils.getLocalHost();      } else if (isInvalidLocalHost(hostToRegistry)) {
          throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
      }      map.put(REGISTER_IP_KEY, hostToRegistry);      // 存儲配置數據
      serviceMetadata.getAttachments().putAll(map);
      // 創建代理
      ref = createProxy(map);
      serviceMetadata.setTarget(ref);
      serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);
      ConsumerModel consumerModel = repository.lookupReferredService(serviceMetadata.getServiceKey());      consumerModel.setProxyObject(ref);
      consumerModel.init(attributes);      initialized = true;      // 發佈 ReferenceConfigInitializedEvent 事件
      dispatch(new ReferenceConfigInitializedEvent(this, invoker));  }

代碼較長,主要是各種配置的檢查和初始化,並收集這些信息加入 map 存儲,以及創建代理。

四、引用服務

接着上面我們繼續看 #createProxy 方法,其不僅執行創建代理的邏輯,同時還會調用其他方法創建、合併 Invoker 實例。

private T createProxy(Map<String, String> map) {
    // 判斷是否本地暴露,包含指定服務 url 直連的情況判斷、或根據參數配置是否進行本地暴露,如協議、scope、injvm 等
    if (shouldJvmRefer(map)) { // 本地引用
        // 創建 URL,協議爲 njvm
        URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
        // 調用 refer 方法創建 InjvmInvoker 實例
        invoker = REF_PROTOCOL.refer(interfaceClass, url);        if (logger.isInfoEnabled()) {
            logger.info("Using injvm service " + interfaceClass.getName());
        }        // 遠程引用
    } else {
        urls.clear();        // 若 url 不爲空
        if (url != null && url.length() > 0) {
            // 配置多個 url 時,用分號分隔
            String[] us = SEMICOLON_SPLIT_PATTERN.split(url);            if (us != null && us.length > 0) {
                for (String u : us) {
                    URL url = URL.valueOf(u);                    if (StringUtils.isEmpty(url.getPath())) {
                        // 設置 url 路徑爲接口全限定名
                        url = url.setPath(interfaceName);                    }                    // 協議爲 registry 時,指定註冊中心
                    if (UrlUtils.isRegistry(url)) {
                        // 將 map 轉換爲查詢字符串,賦值給 refer
                        urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));                    } else {
                        // 合併 url,移除服務提供者的一些配置(這些配置來源於用戶配置的 url 屬性),
                        // 比如線程池相關配置。並保留服務提供者的部分配置,比如版本,group,時間戳等
                        // 最後將合併後的配置設置爲 url 查詢字符串中。
                        urls.add(ClusterUtils.mergeUrl(url, map));                    }                }            }        } else { // 從註冊中心的配置中組裝 URL
            // 協議不是 injvm
            if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
                checkRegistry();                List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
                if (CollectionUtils.isNotEmpty(us)) {
                    for (URL u : us) {
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
                        if (monitorUrl != null) {
                            map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));                        }                        // 添加 refer 參數到 url,並加入 urls 集合
                        urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));                    }                }                // 沒有配置註冊中心,拋出異常
                if (urls.isEmpty()) {
                    throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
                }            }        }        // 只有一個註冊中心或者服務提供者
        if (urls.size() == 1) {
            // 構建 invoker 實例
            invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
        } else { // 多個註冊中心或多個服務提供者,或者兩者混合
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            for (URL url : urls) {
                invokers.add(REF_PROTOCOL.refer(interfaceClass, url));                if (UrlUtils.isRegistry(url)) {
                    // 最後一個註冊中心 URL
                    registryURL = url;                }            }            if (registryURL != null) {
                // 如果註冊中心鏈接不爲空,則將使用 ZoneAwareCluster
                String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME);                // 創建 StaticDirectory 實例,並由 Cluster 對多個 Invoker 進行合併
                invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
            } else { // not a registry url, must be direct invoke.
                String cluster = CollectionUtils.isNotEmpty(invokers)                        ? (invokers.get(0).getUrl() != null ? invokers.get(0).getUrl().getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME) : Cluster.DEFAULT)
                        : Cluster.DEFAULT;                invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers));
            }        }    }    if (shouldCheck() && !invoker.isAvailable()) {
        invoker.destroy();        throw new IllegalStateException("Failed to check the status of the service "
                + interfaceName                + ". No provider available for the service "
                + (group == null ? "" : group + "/")
                + interfaceName +                (version == null ? "" : ":" + version)
                + " from the url "
                + invoker.getUrl()                + " to the consumer "
                + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
    }    if (logger.isInfoEnabled()) {
        logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
    }    // create service proxy
    return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}

在 #createProxy 方法中,首先檢查配置是否是本地暴露,如果是,則根據自適應擴展機制獲取 InjvmProtocol,並調用 #refer 方法生成 InjvmInvoker 實例,完成服務引用。相反,則讀取直連 url 配置,或讀取註冊中心 url ,並將其存儲到 urls 集合中,根據 urls 的大小進行不同的處理。如果 urls 大小爲 1,則直接根據自適應擴展調用調用 #refer 方法生成 invoker 。如果 urls 大於 1,則分別根據 url 生成 invoker,然後再通過 Cluster 合併多個 invoker ,最後調用 ProxyFactory 生成代理類。

五、創建 Invoker

講到服務暴露時,我們同樣分析了 Invoker 的創建過程。Invoker 作爲 Dubbo 的通用模型,代表着一個可執行體。在服務提供者來看,Invoker 用於調用真實的服務實現類;而在服務消費者來看,Invoker 用於執行遠程調用,在上面創建代理的方法中,我們注意到創建 Invoker 的一個關鍵方法 Protocol#refer(Class<T> type, URL url) 。

Protocol 的實現有很多,我們還是以常見的 DubboProtocol 和 RegistryProtocol 來分析 refer 方法如何構建 Invoker 。

1. DubboProtocol

DubboProtocol 繼承了 AbstractProtocol 抽象類,從其 refer 方法入手:

@Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
    }

其調用了模板方法 #protocolBindingRefer(type, url) 。

回到 DubboProtocol#protocolBindingRefer(type, url) 方法:

@Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        optimizeSerialization(url);        // create rpc invoker.
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);
        return invoker;
    }

上面代碼非常簡單,關注其中調用了 #getClients(url) 方法用於獲取客戶端實例,實例類型爲 ExchangeClient。

ExchangeClient 實際上並不具備通信能力,它需要基於更底層的客戶端實例進行通信。比如 NettyClient、MinaClient 等,默認情況下,Dubbo 使用 NettyClient 進行通信。

對於 DubboProtocol 的引用邏輯我們先大概瞭解這麼多,關於集羣、通信後面會詳細說。下面再看 RegistryProtocol 的 refer 方法:

@Override
    @SuppressWarnings("unchecked")
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {        // 通過參數獲取 Registry 的協議,並將其設置爲協議頭
        url = getRegistryUrl(url);
        // 獲取註冊中心實例
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }
        // url 查詢字符串轉換爲 map
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
        // 獲取 group 配置
        String group = qs.get(GROUP_KEY);
        // 多個 group
        if (group != null && group.length() > 0) {
            if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
                return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url);
            }
        }
        Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY));
        return doRefer(cluster, registry, type, url);
    }

繼續看 #doRefer 方法:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        // 創建 RegistryDirectory 實例        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        // 設置註冊中心和協議        directory.setRegistry(registry);        directory.setProtocol(protocol);        // all attributes of REFER_KEY        Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
        // 創建服務消費者 URL        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);        if (directory.isShouldRegister()) {            directory.setRegisteredConsumerUrl(subscribeUrl);            registry.register(directory.getRegisteredConsumerUrl());        }        directory.buildRouterChain(subscribeUrl);        directory.subscribe(toSubscribeUrl(subscribeUrl));        // 將多個服務提供者合併        Invoker<T> invoker = cluster.join(directory);
        // 註冊中心此時沒有其他服務提供者        List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {            return invoker;        }        // 多個服務提供者時,通過 Wrapper 包裹,並通知 RegistryProtocol 的監聽器        RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker);
        for (RegistryProtocolListener listener : listeners) {            listener.onRefer(this, registryInvokerWrapper);        }        return registryInvokerWrapper;    }

如此,生成 Invoker 創建完畢,再根據服務接口生成代理對象,便可執行遠程調用,生成代理的部分邏輯和上篇服務暴露的入口一致,即 ProxyFactory 的 getProxy 方法,感興趣的小夥伴可自行查看。

來源:https://www.tuicool.com/articles/nYZJZzJ

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