dubbo源碼—service reference


service reference

在編寫好服務之后,dubbo會將服務export出去,這個時候就可以編寫consumer來調用這個服務了。dubbo作為一個rpc框架,使用者使用遠程服務和使用本地服務是類似的,不用關心遠程服務在哪里,怎么引用的,因為dubbo包含了自動發現和引用服務的功能。

dubbo引用服務主要工作:

  • 創建proxy和Invoker(DubboInvoker里面會啟動NettyClient)
  • 將consumer注冊到注冊中心
  • 訂閱configurators、providers、routers

通過Java代碼引用

ReferenceConfig<TestDubboService> reference = new ReferenceConfig();
reference.setApplication(new ApplicationConfig(appName));
reference.setRegistry(new RegistryConfig(dubboRegistry));
reference.setInterface(TestDubboService.class);
reference.setTimeout(timeout);
TestDubboService service = (TestDubboService)reference.get();

使用Java代碼配置很明顯,直接使用ReferenceConfig.get獲取一個proxy

通過spring配置引用

<dubbo:reference interface="com.test.service.TestDubboService" id="testDubboService" />

spring解析該dubbo自定義標簽的時候(請提前學習spring如何解析自定義標簽),會初始化ReferenceBean,該bean是一個factoryBean並且繼承自ReferenceConfig,在getBean方法中調用了ReferenceConfig.get,接下來的方式就和上面“使用Java代碼引用”一致了。所以dubbo引用服務的工作就主要在於如何創建proxy。

ReferenceConfig初始化

ReferenceConfig的主要作用是配置並引用遠程服務,創建遠程服務的本地代理。ReferenceBean繼承自ReferenceConfig,ReferenceConfig是一個FactoryBean ,實現了getObject方法,在spring容器初始化完成的時候會初始化配置為非lazyInit的bean,也就會調用ReferenceBean.getObject方法,里面會調用ReferenceConfig.get方法,從而觸發ServiceConfig的初始化方法ServiceConfig.init。

inti方法的主要邏輯是:

  1. 判斷是否已經初始化,get方法是同步方法,所以只需直接判斷標志位initialized即可
  2. 判斷配置的interface是否正確
  3. 判斷-D參數配置或者配置文件是否配置是直連提供者
  4. 配置application、module、registries、monitor等
  5. 檢查stub和mock配置(類似provider端的檢查)
  6. 搜集需要配置到URL中的參數,先將參數收集到map中,URL參數在refer的過程中極其重要,dubbo中的所有配置幾乎都是靠URL傳遞,從URL中獲取或者設置到URL中
  7. 創建遠程服務的本地代理,createProxy

如何創建proxy

由於是遠程服務,consumer需要有一個代理來處理consumer發起的遠程過程調用。dubbo通過遠程調用的可執行體Invoker的代理來實現。接下來主要就是先創建Invoker,然后創建Invoker的proxy。

創建Invoker調用堆棧如下

createProxy的主要功能:

  1. 判斷ReferenceConfig中是否配置了url,如果配置了url,則不從registry中獲取,直接使用配置的url。
  2. 沒有配置url,從registry配置拼裝url
  3. 根據上面配置好的URL來refer對應的服務,創建遠程服務的可執行體Invoker
  4. 將所有的invokers聚合成一個可執行實體MockClusterInvoker
  5. 給MockClusterInvoker創建一個代理類,這個代理類就是我們在consumer端使用的遠程服務代理,該代理實現了對應的service接口,對應的InvocationHandler就是作為代理類構造方法入參的MockClusterInvoker,在后面一節分析中會說明consumer怎么發起調用

下面主要說說Invoker的創建過程:

  1. 和provider一樣,會調用ProtocolFilterWrapper#refer和ProtocolListenerWrapper#refer,分別構造filter鏈和調用對應的listener.referred
  2. RegistryProtocol#refer會判斷是否配置了group
  3. 根據上面的調用堆棧會調用ZookeeperRegistry#doSubscribe,該方法中會訂閱providers、configurators等,並通過notify方法來調用到AbstractRegistry#notify,里面會針對每一個category調用對應listener.notify,consumer端的listener是RegistryDirectory,所以這里會調用RegistryDirectory#notify
  4. RegistryDirectory#notify,這個方法也是registry對應節點變化后監聽的listener,會對每一種監聽的節點類型做處理,這里先只看provider的處理,調用refreshInvoker方法
  5. refreshInvoker方法就是將配置好的url轉換為Invoker,如果轉化后的invoker至少有一個,並且少於原來的invoker(緩存的invoker),則會把廢棄的invoker銷毀掉(destroy)

這里具體說明一下consumer訂閱,ZookeeperRegistry#doSubscribe中會將url中配置的category取出來拼接成registry的目錄節點形式,然后訂閱這些節點

// RegistryProtocol類
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
  // ... 省略中間代碼
  // 這里調用的是RegistryDirectory.subscribe方法
  // 在這里將consumer端想要訂閱的category添加到url,包括providers,configurators,routers
  directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
                                                Constants.PROVIDERS_CATEGORY 
                                                + "," + Constants.CONFIGURATORS_CATEGORY 
                                                + "," + Constants.ROUTERS_CATEGORY));
  return cluster.join(directory);
}

// ZookeeperRegistry在doSubscribe調用自己的下面這個方法,將URL中的category轉化為registry中的目錄對應的url
private String[] toCategoriesPath(URL url) {
  String[] categroies;
  if (Constants.ANY_VALUE.equals(url.getParameter(Constants.CATEGORY_KEY))) {
    // 如果配置的category是*,則取所有的category:providers,consumer,routers,configurators
    categroies = new String[] {Constants.PROVIDERS_CATEGORY, Constants.CONSUMERS_CATEGORY, 
                               Constants.ROUTERS_CATEGORY, Constants.CONFIGURATORS_CATEGORY};
  } else {
    categroies = url.getParameter(Constants.CATEGORY_KEY, new String[] {Constants.DEFAULT_CATEGORY});
  }
  String[] paths = new String[categroies.length];
  for (int i = 0; i < categroies.length; i ++) {
    // 將category拼接成registry中的目錄形式,類似:/dubbo/com.test.service.TestDubboService/providers
    paths[i] = toServicePath(url) + Constants.PATH_SEPARATOR + categroies[i];
  }
  return paths;
}


protected void doSubscribe(final URL url, final NotifyListener listener) {
  try {
    if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
		// ... 省略中間代碼
    } else {
      List<URL> urls = new ArrayList<URL>();
      for (String path : toCategoriesPath(url)) {
        ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
        if (listeners == null) {
          // 如果之前該路徑沒有添加過listener,則創建一個map來放置listener
          zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
          listeners = zkListeners.get(url);
        }
        ChildListener zkListener = listeners.get(listener);
        if (zkListener == null) {
          // 如果沒有添加過對於子節點的listener,則創建
          listeners.putIfAbsent(listener, new ChildListener() {
            public void childChanged(String parentPath, List<String> currentChilds) {
              ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
            }
          });
          zkListener = listeners.get(listener);
        }
        zkClient.create(path, false);
        // 添加listener到該目錄及其子節點
        List<String> children = zkClient.addChildListener(path, zkListener);
        if (children != null) {
          urls.addAll(toUrlsWithEmpty(url, path, children));
        }
      }
      // 這個方法本身會導致監聽的目錄及其子節點變化,直接調用notify
      notify(url, listener, urls);
    }
  } catch (Throwable e) {
    throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
  }
}

上面是consumer的訂閱部分的源碼,在consumer訂閱的時候會調用FailbackRegistry#notify,接下來就是將url轉換為Invoker,接下來的調用鏈路可以參考上面方法調用堆棧的圖,轉化的主要代碼為:

com.alibaba.dubbo.registry.integration.RegistryDirectory#refreshInvoker
com.alibaba.dubbo.registry.integration.RegistryDirectory#toInvokers

這兩個方法中的源代碼注釋較為詳細了就不再贅述了。

在toInvokers方法中會調用DubboProtocol#refer,在該方法中啟動NettyClient。

總結

provider提供服務后,consumer端就可以找到並引用該服務,接下來就可以像使用本地服務一樣使用該服務了,發起遠程該過程調用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM