Dubbo 服務調用 源碼學習(上)(六)

筆記簡述
本學習筆記就來學習dubbo中的消費方是如何和注冊中心打交道的,以及如何實現和服務提供方的直連操作,這其中主要的是invoker生成操作
2018年05月23日22:10:03 添加了3.2.4 刷新invoker一小節,當訂閱服務完成之后,客觀情況下存在服務提供方發生變化的情況,這時候需要刷新RegistryDirectory中已存在的urlInvoker和methodInvoker信息,并且還需要保存對應的cache文件
更多內容可看[目錄]Dubbo 源碼學習

目錄

Dubbo 服務調用 源碼(上)學習(六)
1、Spring
2、ReferenceBean 介紹
3、源碼學習
3.1、生成ReferenceBean的代理對象
3.2、生成Invoker
3.2.1、doRefer
3.2.2、注冊到注冊中心
3.2.3、訂閱服務
3.2.4、刷新invoker
3.2.5、生成Invoker

1、Spring

dubbo的服務使用方是在xml配置了類似于<dubbo:reference interface="com.jwfy.dubbo.product.ProductService" id="productService" />的配置,意味著后續在spring中通過getBean('productService')就可以獲取到遠程代理對象。dubbo:reference 本身映射成為的bean是ReferenceBean,其會存儲整個dubbo需要的各種信息,例如控制中心的注冊地址,服務端的具體IO和端口等。

2、ReferenceBean 介紹

image

如上圖就是ReferenceBean的類圖,根據以往對spring的學習了解,有如下總結:

  • 很清楚的認識到其是一個工廠Bean,后續需要getObject方法得到真正的對象(其實在這里不看源碼,我們就應該能猜到常規做法是通過動態代理生成interface="com.jwfy.dubbo.product.ProductService"中接口對應的proxy對象),如果想獲取ReferenceBean對象本身,則需要使用getBean("&productService")如果這些結論還存在疑問,可以看看之前對spring的源碼學習
  • 通過InitializingBean的afterPropertiesSet方法去為當前的bean注入注冊中心、均衡負責的方式、使用的協議等屬性數據。

在這里生成代理對象是通過Java的動態代理方式,因為指明的是接口,(spring中默認的是如果通過接口生成代理對象,是使用動態代理,否則是使用cglib),那么根據對動態代理知識點的了解,InvocationHandler肯定是跑不掉的,通過invoke去調用執行函數的。

3、源碼學習

3.1、生成ReferenceBean的代理對象

在上文說的getObject方法為入口打斷點,最后可以追蹤到ReferenceConfig類的createProxy方法為真正的生成代理對象的操作。

private T createProxy(Map<String, String> map) {
    URL tmpUrl = new URL("temp", "localhost", 0, map);
    // 這里的臨時url可以是 temp://localhost?application=dubbo-consume&default.check=false
    //&dubbo=2.5.3&interface=com.jwfy.dubbo.product.ProductService&methods=print,getStr&owner=jwfy&pid=15813&side=consumer&timestamp=1525921242794
    final boolean isJvmRefer;
    if (isInjvm() == null) {
        if (url != null && url.length() > 0) { 
            //指定URL的情況下,不做本地引用
            isJvmRefer = false;
        } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
            //默認情況下如果本地有服務暴露,則引用本地服務.
            isJvmRefer = true;
        } else {
            isJvmRefer = false;
        }
    } else {
        isJvmRefer = isInjvm().booleanValue();
    }
    
    if (isJvmRefer) {
          // 如果是本地的服務
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
        invoker = refprotocol.refer(interfaceClass, url);
        if (logger.isInfoEnabled()) {
            logger.info("Using injvm service " + interfaceClass.getName());
        }
    } else {
        if (url != null && url.length() > 0) { 
        // 用戶指定URL,指定的URL可能是對點對直連地址,也可能是注冊中心URL
        // 用戶可以在dubbo:reference 中直接配置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.setPath(interfaceName);
                    }
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        } else { // 只能是通過注冊中心配置拼裝URL
            List<URL> us = loadRegistries(false);
            // 這個false的值含義是非服務提供方,獲取到連接注冊中心的配置
            // 切記?。?!不是獲取服務提供方的url屬性,而是注冊中心的配置
            // 生成的us是類似于registry://127.0.0.1:2182/com.alibaba.dubbo.registry.RegistryService?
            // application=dubbo-consume&client=zkclient&dubbo=2.5.3&group=dubbo-demo&owner=jwfy&pid=1527&registry=zookeeper&timestamp=1525943117155
            if (us != null && us.size() > 0) {
                for (URL u : us) {
                    URL monitorUrl = loadMonitor(u);
                    if (monitorUrl != null) {
                        // 如果存在監控中心,則設置監控屬性
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                }
            }
            if (urls == null || urls.size() == 0) {
                 // 沒有找到一個可用的連接到注冊中心的數據
                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.");
            }
        }
        
        // 在這里interfaceClass說的是 ==> com.jwfy.dubbo.product.ProductService
        // urls還是register協議
        
        // 總之下面這段代碼是非常關鍵的,后面再補充

        if (urls.size() == 1) {
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
            // refprotocol就是包裝了兩次的RegistryProtocol,直接生成對于的invoker
        } else {
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            for (URL url : urls) {
                // 在這里我們能夠猜到其中一定有向注冊中心訂閱獲取所有的服務提供方的信息
                // 并把獲取到的信息返回生成一個invoker對象
                invokers.add(refprotocol.refer(interfaceClass, url));
                if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                    registryURL = url; // 用了最后一個registry url
                }
            }
            if (registryURL != null) { // 有 注冊中心協議的URL
                // 對有注冊中心的Cluster 只用 AvailableCluster
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); 
                invoker = cluster.join(new StaticDirectory(u, invokers));
            }  else { // 不是 注冊中心的URL
                invoker = cluster.join(new StaticDirectory(invokers));
            }
        }
    }

    Boolean c = check;
    if (c == null && consumer != null) {
        c = consumer.isCheck();
    }
    if (c == null) {
        c = true; // default true
    }
    if (c && ! invoker.isAvailable()) {
        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());
    }
    // 創建服務代理
    // 上面已經說了proxyFactory是StubProxyFactoryWrapper包裝了JavassistProxyFactory類
    return (T) proxyFactory.getProxy(invoker);
}

JavassistProxyFactory 類

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
image

3.2、生成Invoker

Invoker是對可執行對象的引用,需要明確可引用的具體位置(服務端的明確IP和端口等信息)

現在我們已經拿到了當前服務的注冊中心的配置,那么接下來就需要連接到注冊中心,并獲取到可以調用的機器情況(現實開發中,分布式系統基本上都存在多個機器信息),并組合成為需要的invoker。

下面這個代碼段就是生產具體的Invoke操作,接下來好好分析一下

invoker = refprotocol.refer(interfaceClass, urls.get(0));
// refprotocol就是包裝了兩次的RegistryProtocol,直接生成對于的invoker
// url是registry開頭的協議,在參數中包含了消息等協議和其參數
// interfaceClass是接口類

在分析源碼之前,如果換成我們,我們會完成什么操作呢?

  • 向注冊中心訂閱消費者,這樣通過dubbo-admin就可以觀察現有的生產者和消費者
  • 從主存中心 獲取 生產者的信息
  • 類似均衡負責的操作,選擇合適的生產者
  • 直連生產者,獲取結果

RegistryProtocol 類

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
   url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
   // 替換協議,一般情況下都是替換成為zookeeper
   Registry registry = registryFactory.getRegistry(url);
   // 這一步是獲取到注冊中心的配置信息,這里和服務暴露的操作一致
   // 其中的key是zookeeper://127.0.0.1:2182/dubbo-demo/com.alibaba.dubbo.registry.RegistryService
   // 那么同一個jvm內的key其實都是一致的,所以其連接到控制中心的數據也一致
   // 如果沒有連接,則需要創建一個新的鏈接實體,其中會把當前的信息存儲到文件中
   // 也會有對應的監聽者等,然后真正的調用連接zk操作,確保zk是真實存在的
   if (RegistryService.class.equals(type)) {
    // 如果類型是RegistryService,則使用getInvoke操作直接拼接生成一個invoke對象
    // 這個操作和服務提供方生成invoker的方式一致
       return proxyFactory.getInvoker((T) registry, type, url);
   }

   // group="a,b" or group="*"
   Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
   // 這個qs包含了需要生成invoker的所有參數,例如接口名稱,函數名,所述范疇(消費者)等信息
   String group = qs.get(Constants.GROUP_KEY);
   // 查看分組信息
   if (group != null && group.length() > 0 ) {
       if ( ( Constants.COMMA_SPLIT_PATTERN.split( group ) ).length > 1
               || "*".equals( group ) ) {
           return doRefer( getMergeableCluster(), registry, type, url );
       }
   }
   
   // 看有分組和沒分組,其實就是集群cluster不一樣
   return doRefer(cluster, registry, type, url);
}

private Cluster getMergeableCluster() {
   return ExtensionLoader.getExtensionLoader(Cluster.class).getExtension("mergeable");
   // 生成MergeableCluster 集群
   // 如下代碼Cluster$Adpative類則就是cluster,默認的是為FailoverCluster集群
}

Cluster$Adpative 類

package com.alibaba.dubbo.rpc.cluster;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Cluster$Adpative implements com.alibaba.dubbo.rpc.cluster.Cluster {
   public com.alibaba.dubbo.rpc.Invoker join(
       com.alibaba.dubbo.rpc.cluster.Directory arg0)
       throws com.alibaba.dubbo.rpc.cluster.Directory {
       if (arg0 == null) {
           throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
       }

       if (arg0.getUrl() == null) {
           throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");
   }

       com.alibaba.dubbo.common.URL url = arg0.getUrl();
       String extName = url.getParameter("cluster", "failover");

       if (extName == null) {
           throw new IllegalStateException(
               "Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" +
               url.toString() + ") use keys([cluster])");
       }

       com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class)
                                                                                                                .getExtension(extName);

       return extension.join(arg0);
   }
}

3.2.1、doRefer

經過上面的操作,現在來到了doRefer函數操作,其結果返回的就是invoker對象

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    // cluster 集群,目前可能為FailoverCluster,如果在有分組的情況下則是MergeableCluster
    // registry 注冊中心信息
    // type 就是上面說的
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
    if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
            // 接口名字有意義而且是注冊協議
            // 把當前url信息當做消費者節點注冊到注冊中心中區
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
            Constants.PROVIDERS_CATEGORY 
            + "," + Constants.CONFIGURATORS_CATEGORY 
            + "," + Constants.ROUTERS_CATEGORY));
        
    // 訂閱服務,并生成invoker對象
    return cluster.join(directory);
}

在上面一筆帶過了如何注冊、訂閱、生成invoke的,接下來依次拆分各個細節

3.2.2、注冊到注冊中心

registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY, Constants.CHECK_KEY, String.valueOf(false)));
  • registry是ZookeeperRegistry對象
  • subscribeUrl是consumer://192.168.10.123/com.jwfy.dubbo.product.ProductService?application=dubbo-consume&default.check=false&dubbo=2.5.3&interface=com.jwfy.dubbo.product.ProductService&methods=print,getStr&owner=jwfy&pid=1196&side=consumer&timestamp=1526204222984

表示是一個消費者

進入到FailbackRegistry類

public void register(URL url) {
    super.register(url);
    // 會把當前的url添加到registered集合中,表示該url注冊了
    failedRegistered.remove(url);
    failedUnregistered.remove(url);
    // 既然都要注冊了,肯定從失敗的集合和取消注冊的集合中移除掉
    try {
        // 向服務器端發送注冊請求,也是真正的開始注冊操作
        doRegister(url);
    } catch (Exception e) {
        Throwable t = e;

        // 如果開啟了啟動時檢測,則直接拋出異常
        boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                && url.getParameter(Constants.CHECK_KEY, true)
                && ! Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
        boolean skipFailback = t instanceof SkipFailbackWrapperException;
        if (check || skipFailback) {
            if(skipFailback) {
                t = t.getCause();
            }
            throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
        } else {
            logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
        }

        // 將失敗的注冊請求記錄到失敗列表,定時重試
        failedRegistered.add(url);
    }
}

// 關于failedRegistered的重試操作,在構造函數中有
// 開啟定時任務運行的操作,默認時間是5秒執行一次
    this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
        public void run() {
            // 檢測并連接注冊中心
            try {
                retry();
                // 里面肯定有遍歷failedRegistered集合的doRegister操作(事實上確實有)
            } catch (Throwable t) { // 防御性容錯
                // 個人覺得這個代碼寫的很好,因為突出一個點,什么時候拋出異常,什么時候處理異常
                // 這點問題上,自己曾經踩了很多坑,也發現很多人也有這個毛病
                logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
            }
        }
    }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);

在doRegister操作中,是利用zk的API存儲如下的path
/dubbo-jwfy/com.jwfy.dubbo.product.ProductService/consumers/consumer%3A%2F%2F192.168.10.123%2Fcom.jwfy.dubbo.product.ProductService%3Fapplication%3Ddubbo-consume%26category%3Dconsumers%26check%3Dfalse%26default.check%3Dfalse%26dubbo%3D2.5.3%26interface%3Dcom.jwfy.dubbo.product.ProductService%26methods%3Dprint%2CgetStr%26owner%3Djwfy%26pid%3D1196%26side%3Dconsumer%26timestamp%3D1526204222984

如下圖,在調用doRegister前后zk注冊中心節點的情況,很明顯已經注冊成功


image

3.2.3、訂閱服務

服務訂閱說通俗些就是獲取zk中需要的節點信息,本例中是獲取生產者的連接信息

directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
            Constants.PROVIDERS_CATEGORY 
            + "," + Constants.CONFIGURATORS_CATEGORY 
            + "," + Constants.ROUTERS_CATEGORY));
// 這個url 添加了生產者、配置、路由三個節點的配置參數
  • url是consumer://192.168.10.123/com.jwfy.dubbo.product.ProductService?application=dubbo-consume&category=providers,configurators,routers&default.check=false&dubbo=2.5.3&interface=com.jwfy.dubbo.product.ProductService&methods=print,getStr&owner=jwfy&pid=1196&side=consumer&timestamp=1526204222984
  • directory 是RegistryDirectory,其中參數registry就是上面的注冊中心ZookeeperRegistry的配置

FailbackRegistry 類

public void subscribe(URL url, NotifyListener listener) {
    super.subscribe(url, listener);
    // 會設置registry的subscribed信息,其中subscribed是一個Map<Url,Set<NotifyListener>>的容器
    removeFailedSubscribed(url, listener);
    try {
        // 向服務器端發送訂閱請求,真正干活的來了
        doSubscribe(url, listener);
    } catch (Exception e) {
        Throwable t = e;

        List<URL> urls = getCacheUrls(url);
        if (urls != null && urls.size() > 0) {
            notify(url, listener, urls);
            logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
        } else {
            // 如果開啟了啟動時檢測,則直接拋出異常
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true);
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if(skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
            } else {
                logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
            }
        }

        // 將失敗的訂閱請求記錄到失敗列表,定時重試
        // 和上面注冊的套路一致
        addFailedSubscribed(url, listener);
    }
}

來到了doSubscribe方法,在這里我們將會了解到如何從注冊中心獲取到生產者的連接信息的

protected void doSubscribe(final URL url, final NotifyListener listener) {
    try {
        if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
            // Constants.ANY_VALUE是*
            // url.getServiceInterface 是 com.jwfy.dubbo.product.ProductService
            String root = toRootPath();
            ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
            if (listeners == null) {
                zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                listeners = zkListeners.get(url);
            }
            ChildListener zkListener = listeners.get(listener);
            if (zkListener == null) {
                listeners.putIfAbsent(listener, new ChildListener() {
                    public void childChanged(String parentPath, List<String> currentChilds) {
                        for (String child : currentChilds) {
                            if (! anyServices.contains(child)) {
                                anyServices.add(child);
                                subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child, 
                                        Constants.CHECK_KEY, String.valueOf(false)), listener);
                            }
                        }
                    }
                });
                zkListener = listeners.get(listener);
            }
            zkClient.create(root, false);
            List<String> services = zkClient.addChildListener(root, zkListener);
            if (services != null && services.size() > 0) {
                anyServices.addAll(services);
                for (String service : services) {
                    subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service, 
                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                }
            }
        } else {
            List<URL> urls = new ArrayList<URL>();
            for (String path : toCategoriesPath(url)) {
                // 這個就是上面說的三個目錄節點,生產者,配置,路由 
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                // zkListeners 是 Map<URL, Map<NotifyListener, ChildListener>>
                // 管理url與其對應的監聽者和zk監聽者的映射關系
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);
                // 從集合中獲取到zk的監聽者
                if (zkListener == null) {
                    // 如果沒有,則需要手動創建一個新的,其中包含了自定義實現的更新子節點的函數操作
                    // 里面會調用notify訂閱方法(這個方法很重要,由zkClient主動調用childChanged方法)
                    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);
                // 創建永久節點,此時的path可能為/dubbo-jwfy/com.jwfy.dubbo.product.ProductService/providers
                List<String> children = zkClient.addChildListener(path, zkListener);
                // 重點來了。。。。。這里將會獲取到對應生產者的連接信息,具體看下面代碼
                if (children != null) {
                    urls.addAll(toUrlsWithEmpty(url, path, children));
                }
            }
            notify(url, listener, urls);
        }
    } catch (Throwable e) {
        throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

AbstractZookeeperClient 類

public List<String> addChildListener(String path, final ChildListener listener) {
    ConcurrentMap<ChildListener, TargetChildListener> listeners = childListeners.get(path);
    // 從zkClient本身的childListeners獲取path的監聽者信息
    if (listeners == null) {
        childListeners.putIfAbsent(path, new ConcurrentHashMap<ChildListener, TargetChildListener>());
        listeners = childListeners.get(path);
    }
    TargetChildListener targetListener = listeners.get(listener);
    // 再獲取目標節點信息(這個就包含了生產者的信息了)
    if (targetListener == null) {
        listeners.putIfAbsent(listener, createTargetChildListener(path, listener));
        targetListener = listeners.get(listener);
    }
    return addTargetChildListener(path, targetListener);
}


public List<String> addTargetChildListener(String path, final IZkChildListener listener) {
    return client.subscribeChildChanges(path, listener);
    // 這一步就會深入到zkClientjar包內進行最后的_zk.getChildren操作
    // 返回的List<String> 就是對應的path路徑的內容
    
    // 此時path 是 /dubbo-jwfy/com.jwfy.dubbo.product.ProductService/providers
    // 返回的內容 是 dubbo%3A%2F%2F192.168.10.123%3A20880%2Fcom.jwfy.dubbo.product.ProductService
    // %3Fanyhost%3Dtrue%26application%3Ddubbo-demo%26default.loadbalance%3Drandom%26
    // dubbo%3D2.5.3%26interface%3Dcom.jwfy.dubbo.product.ProductService%26
    // methods%3Dprint%2CgetStr%26owner%3Djwfy%26pid%3D1081%26side%3Dprovider%26
    // timestamp%3D1526198287386%26token%3Dfdfdf
}

現在完成了和注冊中心的操作了,通過path順利拿到生產者的信息,如果仔細觀察上述的參數信息,會發現pid是1081,再看看jps顯示的進程號,如下圖,恰好說明獲取到的生產者信息是對的


image

接著來到toUrlsWithEmpty函數,如果有仔細觀察這個方法會發現,參數列表都改成了(URL consumer, String path, List<String> providers)這已經很明確的告訴我們,第一個參數是消費者的url,第二個是當前zk的path信息,第三個是獲取到的生產者列表信息(為啥是列表呢?因為生產者可以是多個,而且存在多個的情況下,后續均衡負責還需要選擇一個可用的生產者進行網絡信息交互操作)

當然toUrlsWithEmpty函數主要是進行生產者和消費者的url信息對比操作,如果沒有合適的url則添加一個empty協議的url信息(后期就是通過這個empty判斷是否存在有用的生產者,日常開發中的無效黑白名單的錯誤就產生在這里

3.2.4、刷新invoker

緊接著來到了notify方法,如下圖的具體各個參數具體值


image

緊接著來到了AbstractRegistry類

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
    // url 是注冊到注冊到注冊中心的url
    // listener是監聽器,其實就是RegistryDirectory
    // urls 是 從注冊中心獲取到到的 服務提供方的url集合
    if (url == null) {
        throw new IllegalArgumentException("notify url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("notify listener == null");
    }
    if ((urls == null || urls.size() == 0) 
            && ! Constants.ANY_VALUE.equals(url.getServiceInterface())) {
        logger.warn("Ignore empty notify urls for subscribe url " + url);
        return;
    }
    if (logger.isInfoEnabled()) {
        logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
    }
    Map<String, List<URL>> result = new HashMap<String, List<URL>>();
    for (URL u : urls) {
        if (UrlUtils.isMatch(url, u)) {
           // isMatch(URL consumerUrl, URL providerUrl) 函數的參數
           // 已經非常清楚的說明了url是服務調用方的url,u是服務提供方的url
           // 也和我們上面的描述是一致的
           // 對兩個url的接口、類目、enable、分組、版本號、classifier等內容進行匹配
           // 如果匹配合適,就認為為true
           // 沒有針對協議進行匹配操作
            String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            List<URL> categoryList = result.get(category);
            if (categoryList == null) {
                categoryList = new ArrayList<URL>();
                result.put(category, categoryList);
            }
            categoryList.add(u);
        }
    }
    // 按照類目進行分組成為一個map
    
    if (result.size() == 0) {
        return;
    }
    Map<String, List<URL>> categoryNotified = notified.get(url);
    if (categoryNotified == null) {
        notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
        categoryNotified = notified.get(url);
    }
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        // 替換notified.get(url)的map信息
        saveProperties(url);
        // 初次看,這個url就是上面的服務調用方的url信息,沒必要每次都保存吧
        // 進入到這個函數可以發現,他會每次又調用notified.get(url)去獲取最新的map數據,默認為異步保存數據
        listener.notify(categoryList);
        // 接著來到了RegistryDirectory的notify方法
    }
}

RegistryDirectory 類

public synchronized void notify(List<URL> urls) {
    List<URL> invokerUrls = new ArrayList<URL>();
    List<URL> routerUrls = new ArrayList<URL>();
    List<URL> configuratorUrls = new ArrayList<URL>();
    for (URL url : urls) {
        String protocol = url.getProtocol();
        String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
        if (Constants.ROUTERS_CATEGORY.equals(category) 
                || Constants.ROUTE_PROTOCOL.equals(protocol)) {
            routerUrls.add(url);
        } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) 
                || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
            configuratorUrls.add(url);
        } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
            invokerUrls.add(url);
        } else {
            logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
        }
    }
    // configurators 
    if (configuratorUrls != null && configuratorUrls.size() >0 ){
        this.configurators = toConfigurators(configuratorUrls);
    }
    // routers
    if (routerUrls != null && routerUrls.size() >0 ){
        List<Router> routers = toRouters(routerUrls);
        if(routers != null){ // null - do nothing
            setRouters(routers);
        }
    }
    List<Configurator> localConfigurators = this.configurators; // local reference
    // 合并override參數
    this.overrideDirectoryUrl = directoryUrl;
    if (localConfigurators != null && localConfigurators.size() > 0) {
        for (Configurator configurator : localConfigurators) {
            this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
        }
    }
    // invokerUrls只能是服務提供方目錄去刷新已存的invoke
    refreshInvoker(invokerUrls);
}

private void refreshInvoker(List<URL> invokerUrls){
    if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
            && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            // empty協議,而且只有1個
            // 需要注意到這個方法只有服務提供方的url才可以被調用
            // 服務提供方只包含了一個empty協議的無效url,設置forbidden=true
            // 這個true就是后面dubbo中經常出現的黑白名單錯誤情況
        this.forbidden = true; // 禁止訪問
        this.methodInvokerMap = null; // 置空列表
        destroyAllInvokers(); // 關閉所有Invoker
    } else {
        this.forbidden = false; // 允許訪問
        Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
        if (invokerUrls.size() == 0 && this.cachedInvokerUrls != null){
            invokerUrls.addAll(this.cachedInvokerUrls);
        } else {
            this.cachedInvokerUrls = new HashSet<URL>();
            this.cachedInvokerUrls.addAll(invokerUrls);//緩存invokerUrls列表,便于交叉對比
        }
        if (invokerUrls.size() ==0 ){
            return;
        }
        // 后面的操作就是去刷新現存的invoker列表
        
        Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls) ;// 將URL列表轉成Invoker列表
        // 這個invoker是InvokerDelegete類,是包裝了一層的InvokerWrapper
        Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 換方法名映射Invoker列表
        // state change
        //如果計算錯誤,則不進行處理.
        if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0 ){
            logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :"+invokerUrls.size() + ", invoker.size :0. urls :"+invokerUrls.toString()));
            return ;
        }
        this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
        this.urlInvokerMap = newUrlInvokerMap;
        try{
            destroyUnusedInvokers(oldUrlInvokerMap,newUrlInvokerMap); // 關閉未使用的Invoker
        }catch (Exception e) {
            logger.warn("destroyUnusedInvokers error. ", e);
        }
    }
}

這個函數具體的操作包含了
1、更新ZookeeperRegistry中的notify參數信息(把生產者、配置、路由等url信息存入其中
2、保存節點信息文檔到dubbo的cache文件中
3、刷新生成invoke的數據信息

到此整個的訂閱服務的操作就完成了

3.2.5、生成Invoker

現在就剩下最關鍵的一句話cluster.join(directory),會層層包裝,最后形成的invoker如圖所示

image

在directory中包含了所有的注冊信息,在后面的真正的函數調用其實也是通過invoker.invoker去調用執行

InvokerInvocationHandler 類

被包裝的類通過動態代理反射時,內嵌入的InvocationHandler類,具體方法調用則會進入到invoke方法中

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    Class<?>[] parameterTypes = method.getParameterTypes();
    if (method.getDeclaringClass() == Object.class) {
        return method.invoke(invoker, args);
    }
    if ("toString".equals(methodName) && parameterTypes.length == 0) {
        return invoker.toString();
    }
    if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
        return invoker.hashCode();
    }
    if ("equals".equals(methodName) && parameterTypes.length == 1) {
        return invoker.equals(args[0]);
    }
    return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
image

由于后續的流程太多,接下來的內容分為一小節學習,到此invoker就生成了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • dubbo暴露服務有兩種情況,一種是設置了延遲暴露(比如delay="5000"),另外一種是沒有設置延遲暴露或者...
    加大裝益達閱讀 21,318評論 5 36
  • 在上一篇文章我們講解了一下 dubbo 服務暴露過程中的本地暴露。它只是一個開胃小菜,主要是為我們后面講解遠程暴露...
    carl_zhao閱讀 354評論 0 1
  • 今天在去拍攝的路上,有個想法給聞濤分享,贏家教練的手語舞《國家》跳的那麼好,那麼有感覺,為什麼我們不可以選一個陽光...
    粟莎閱讀 167評論 0 0
  • “我還沒有問你,為什么打你的電話打不通,微信不回,門也不出?!?“還有啊,住在喬洛家那個女孩子,就是上次在三亞見過...
    羥羊閱讀 163評論 0 1
  • ① 嘴角裂:脾有火肺氣不足。 建議: 綠豆百合湯。 ② 嘴唇干:肝腎陰虛。 建議: 枸杞、麥冬泡水喝。 ③ 口氣大...
    財知道閱讀 218評論 0 0