dubbo原始碼分析02:服務引用

phoenix發表於2019-06-28

一、何時建立服務引用 


  引用官方文件的原話,如果將Dubbo託管在Spring-IOC容器下,Dubbo服務引用的時機有兩個,第一個是在Spring容器呼叫ReferenceBean的afterPropertiesSet方法時引用服務,第二個是在ReferenceBean對應的服務被注入到其他類中時引用。這兩個引用服務的時機區別在於,第一個是餓漢式的,第二個是懶漢式的。預設情況下,Dubbo使用懶漢式引用服務。如果需要使用餓漢式,可通過配置 <dubbo:reference> 的init屬性開啟。下面我們按照Dubbo預設配置進行分析,整個分析過程從ReferenceBean的getObject方法開始。當我們的服務被注入到其他類中時,Spring會第一時間呼叫getObject方法,並由該方法執行服務引用邏輯。按照慣例,在進行具體工作之前,需先進行配置檢查與收集工作。接著根據收集到的資訊決定服務用的方式,有三種,第一種是引用本地(JVM)服務,第二是通過直連方式引用遠端服務,第三是通過註冊中心引用遠端服務。不管是哪種引用方式,最後都會得到一個Invoker例項。如果有多個註冊中心,多個服務提供者,這個時候會得到一組Invoker例項,此時需要通過叢集管理類Cluster將多個Invoker合併成一個例項。合併後的Invoker例項已經具備呼叫本地或遠端服務的能力了,但並不能將此例項暴露給使用者使用,這會對使用者業務程式碼造成侵入。此時框架還需要通過代理工廠類(ProxyFactory)為服務介面生成代理類,並讓代理類去呼叫 Invoker邏輯。避免了Dubbo框架程式碼對業務程式碼的侵入,同時也讓框架更容易使用。
  直接從ReferenceConfig開始分析,API的形式使用dubbo時,設定好ReferenceConfig例項的各種引數後,呼叫get方法獲取服務引用例項。另外,需要了解一點dubbo的配置,可以參考官網的“schema配置參考手冊”。
 

二、服務引用的建立流程


  該類的get方法為獲取服務引用方法。該方法比較簡單,首先檢查銷燬標識“destroyed”,如果為true,表示該引用已經被銷燬,不應再進行使用。接著檢查服務引用是否為空,如果為空,則呼叫init方法進行初始化,否則返回該服務引用。
public synchronized T get() {
    if (destroyed) {
        throw new IllegalStateException("Already destroyed!");
    }
    if (ref == null) {
        init();
    }
    return ref;
}

  下面來重點看看init方法的原始碼,追蹤一下服務引用是如何建立的,方法比較長,有較長的篇幅都在進行引數的校驗、補全,需要點耐心看完。

private void init() {
    //檢查初始化標識,防止重複初始化
    if (initialized) {
        return;
    }
    initialized = true;
    
    //檢查介面名是否合法
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("<dubbo:reference interface=\"\"... not allow null!");
    }
    
    //檢查ConsumerConfig變數是否為空(ConsumerConfig為ReferenceConfig提供了某些屬性的預設值):
    //(1)如果ConsumerConfig為null,則new一個;
    //(2)呼叫appendProperties(AbstractConfig config)方法完善ConsumerConfig的配置;
    checkDefault();
    
    //呼叫appendProperties(AbstractConfig config)方法完善ReferenceConfig的配置,該方法邏輯如下:
    //(1)檢查AbstractConfig中每一個setXXX(原始型別)或isXXX(原始型別)的方法,對XXX屬性進行配置;
    //(2)按優先順序從高到低的順序,依次從System.getProperty、配置中心、AbstractConfig對應getXXX返回值、
    //dubbo本地配置檔案中進行查詢XXX的屬性值並進行設定;
    appendProperties(this);
    
    //設定成員變數“泛化引用標識”,如果為空則從成員變數ConsumerConfig中獲取該標識的值
    if (getGeneric() == null && getConsumer() != null) {
        setGeneric(getConsumer().getGeneric());
    }
    
    //判斷泛化標識的值是否為真,做這個判斷的原因是因為泛化標識為字串型別
    if (ProtocolUtils.isGeneric(getGeneric())) {
        //如果為真,則將interfaceClass設定為GenericService
        interfaceClass = GenericService.class;
    } else {
        //如果為假,則通過當前的類載入器載入interfaceName,獲取interfaceClass
        try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        //(1)校驗interfaceClass是否為null、是否為介面型別;
        //(2)如果配置了List<MethodConfig>,需要校驗interfaceClass是否有相應的方法
        checkInterfaceAndMethods(interfaceClass, methods);
    }
    
    /****************************** begin ******************************/  
    //下面程式碼塊的作用是嘗試從系統屬性或配置檔案中獲取interfaceName的配置,
    //該配置值賦給成員變數String url,用於服務消費方點對點呼叫服務提供方。    
    String resolve = System.getProperty(interfaceName);
    String resolveFile = null;
    if (resolve == null || resolve.length() == 0) {
        resolveFile = System.getProperty("dubbo.resolve.file");
        if (resolveFile == null || resolveFile.length() == 0) {
            File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
            if (userResolveFile.exists()) {
                resolveFile = userResolveFile.getAbsolutePath();
            }
        }
        if (resolveFile != null && resolveFile.length() > 0) {
            Properties properties = new Properties();
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(new File(resolveFile));
                properties.load(fis);
            } catch (IOException e) {
                throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
            } finally {
                try {
                    if (null != fis) fis.close();
                } catch (IOException e) {
                    logger.warn(e.getMessage(), e);
                }
            }
            resolve = properties.getProperty(interfaceName);
        }
    }
    if (resolve != null && resolve.length() > 0) {
        url = resolve;
          //省略了日誌列印的程式碼
    }    
    /****************************** end ******************************/  
    
    /****************************** begin ******************************/  
    //下面程式碼塊的作用是檢測ApplicationConfig、ModuleConfig、RegistryConfig、MonitorConfig
    //這幾個核心配置是否為空。如果為空,則嘗試從其他配置中獲取。
    if (consumer != null) {
        if (application == null) {
            application = consumer.getApplication();
        }
        if (module == null) {
            module = consumer.getModule();
        }
        if (registries == null) {
            registries = consumer.getRegistries();
        }
        if (monitor == null) {
            monitor = consumer.getMonitor();
        }
    }
    if (module != null) {
        if (registries == null) {
            registries = module.getRegistries();
        }
        if (monitor == null) {
            monitor = module.getMonitor();
        }
    }
    if (application != null) {
        if (registries == null) {
            registries = application.getRegistries();
        }
        if (monitor == null) {
            monitor = application.getMonitor();
        }
    }
    //類似於checkDefault方法檢查ConsumerConfig,該方法檢查ApplicationConfig是否為空並完善其各欄位值
    checkApplication(); 
    //檢查ReferenceConfig的local、stub、mock配置項是否正確
    checkStubAndMock(interfaceClass);
    /****************************** end ******************************/  
    
    /****************************** start ******************************/  
    //下面程式碼塊的作用是收集配置,並將配置儲存在一個map中
    Map<String, String> map = new HashMap<String, String>();
    Map<Object, Object> attributes = new HashMap<Object, Object>();
    map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE); //side=consumer
    map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion()); //dubbo=2.6.2
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis())); //timestamp=時間戳
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid())); //pid=程式pid
    }
    //判斷是否是泛化引用
    if (!isGeneric()) {
        //非泛化引用,設定revision=interfaceClass的jar版本號
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
            map.put("revision", revision);
        }
        //設定介面方法,methods=xxx1,xxx2,...
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        if (methods.length == 0) {
            logger.warn("NO method found in service interface " + interfaceClass.getName());
            map.put("methods", Constants.ANY_VALUE);
        } else {
            map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    map.put(Constants.INTERFACE_KEY, interfaceName); //interface=interfaceName
    //將ApplicationConfig、ModuleConfig、ConsumerConfig、ReferenceConfig的值設定到map中,
    //(1)獲取方法名為getXXX或者isXXX、public、無方法引數、返回值為原始值的非getClass方法;
    //(2)獲取(1)方法上的@Paramter註解,根據該註解的excluded、escaped、append屬性判斷該屬性值
    //是否需要被忽略、是否需要URLEncoded、是否需要以追加的形式設定入map(","作為追加值分隔符);
    //(3)獲取方法名為getParameters、public、無方法引數、返回值為Map的方法;
    //(4)將(3)中的方法返回值Map的key-value鍵值對做key處理之後(新增字首、"-"變"."),設定入map;
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, consumer, Constants.DEFAULT_KEY);
    appendParameters(map, this);
    /****************************** end ******************************/  

    /****************************** start ******************************/  
    //下面程式碼塊的作用是處理MethodConfig 例項。該例項包含了事件通知配置如onreturn、onthrow、oninvoke等。
    //由於一般不會使用到MethodConfig配置,我們先暫時忽略這個配置的程式碼
    String prefix = StringUtils.getServiceKey(map);
    if (methods != null && !methods.isEmpty()) {
        for (MethodConfig method : methods) {
            appendParameters(map, method, method.getName());
            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
            appendAttributes(attributes, method, prefix + "." + method.getName());
            checkAndConvertImplicitConfig(method, map, attributes);
        }
    }
    /****************************** end ******************************/  

    /****************************** start ******************************/  
    //下面程式碼塊的作用是設定服務消費者的IP並儲存到map中
    String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
    if (hostToRegistry == null || hostToRegistry.length() == 0) {
        hostToRegistry = NetUtils.getLocalHost();
    } else if (isInvalidLocalHost(hostToRegistry)) {
        throw new IllegalArgumentException("Specified invalid registry ip from property ... value: ...");
    }
    map.put(Constants.REGISTER_IP_KEY, hostToRegistry); //register.ip=實際的IP地址
    /****************************** end ******************************/  

    //將attributes存入靜態上下文
    StaticContext.getSystemContext().putAll(attributes);
    
    //根據map建立服務應用代理,下一小節將對該方法進行詳細說明
    ref = createProxy(map);
    
    //將服務介面名、ReferenceConfig、服務引用例項、服務介面方法包裝成ConsumerModel並存入ApplicationModel
    ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
    ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}

 

三、建立代理物件


  本小節對createProxy方法重點講解,它是初始化Reference中最重要的方法:
private T createProxy(Map<String, String> map) {
    //構建臨時的URL,構造方法引數依次為protocol、host、port、parameter
    URL tmpUrl = new URL("temp", "localhost", 0, map);
    
     //判斷是否是JVM內的引用
    final boolean isJvmRefer; 
    if (isInjvm() == null) {       
        if (url != null && url.length() > 0) { 
            //點對點直連引數"url"有值,則不為JVM內引用
            isJvmRefer = false;
        } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
            //呼叫InjvmProtocol的isInjvmRefer方法,判斷是否是JVM內引用
            isJvmRefer = true;
        } else {
            //預設不是JVM內引用
            isJvmRefer = false;
        }
    } else {
        //獲取injvm配置值
        isJvmRefer = isInjvm().booleanValue();
    }
    
    /******************************* injvm呼叫 *******************************/
    if (isJvmRefer) {
        //如果是JVM內的引用,則建立JVM呼叫的上下文URL,protocol=injvm,host=127.0.0.1,
        //port=0,path=interfaceClass.getName(),並且將引數map設定入URL的parameters中
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);        
        //refprotocol為SPI介面Protocol的自適應擴充套件類,且refer方法有@Adaptive註解,
        //運用SPI機制原始碼分析中的知識,Protocol介面的自適應擴充套件類的refer程式碼,
        //會通過呼叫URL型別引數的getProtocol方法得到實際應該獲取到的擴充套件類name,即injvm。
        //在原始碼的dubbo-rpc-injvm模組下,找到protocol的配置檔案,
        //其中配置了injvm的擴充套件類為org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol。
        //那麼這裡獲取到的invoker即為InjvmProtocol.refer的返回結果,即InjvmInvoker
        invoker = refprotocol.refer(interfaceClass, url);        
    } else {
        /******************************* 點對點直連呼叫 *******************************/   
        //如果成員變數url不為空,表示要做直連呼叫。url是一個String,維護服務提供者地址
        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物件。地址字串支援協議頭、驗證使用者密碼、IP、PORT、
                    //等其他呼叫引數(如protocol)
                    URL url = URL.valueOf(u);
                    //如果地址字串中未包含服務路徑,則進行補全,即介面的全限定名
                    if (url.getPath() == null || url.getPath().length() == 0) {
                        url = url.setPath(interfaceName);
                    }
                    //檢測url協議是否為registry,若是,表明使用者想使用指定的註冊中心
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        //將map轉換為查詢字串,並作為refer引數的值新增到url的map欄位中,
                        //注意:這裡通過字串生成的url還沒維護該方法的傳入引數map。
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, 
                                                            StringUtils.toQueryString(map)));
                    } else {
                        //合併使用者通過直連地址字串配置的呼叫引數與其他途徑配置的呼叫引數:
                        //(1)移除服務提供者的一些配置(這些配置來源於使用者配置的url屬性),如執行緒池相關配置;
                        //(2)保留服務提供者的部分配置,比如版本,group,時間戳等;
                        //(3)最後將合併後的配置設定為url查詢字串中。
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }        
        } else {  
            /******************************* 通過註冊中心呼叫 *******************************/
            //載入註冊中心配置List<Registry>,轉換為List<URL>
            List<URL> us = loadRegistries(false);
            //遍歷註冊中心URL
            if (us != null && !us.isEmpty()) {
                for (URL u : us) {
                    //獲取監控URL
                    URL monitorUrl = loadMonitor(u);
                    if (monitorUrl != null) {
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
                    //(1)將本方法的請求引數map轉換為查詢字串形式"key1=value1&key2=value2...";
                    //(2)以refer作為key,(1)的結果encoded作為value,存入註冊中心URL的paramters引數中;
                    //(3)將註冊中心URL存入ReferenceConfig的全域性變數List<URL> urls中;
                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                }
            }
            if (urls == null || urls.isEmpty()) {
                throw new IllegalStateException("No such any registry to reference ...");
            }
        }
              
        //如果成員變數List<URL> urls大小為1,則直接通過Protocol自適應擴充類構建Invoker例項介面。
        //該自適應擴充套件類由位元組碼技術生成,其refer方法具有@Adaptive註解,根據SPI機制的原始碼知識,
        //refer方法按照引數URL中getProtocol的值查詢實際的擴充套件類例項,如果getProtocol沒有值,
        //則取Protocol介面的@SPI註解value值"dubbo"作為name查詢擴充套件類例項。一般來說,如果通過註冊
        //中心進行呼叫,則getProtocol獲取到的值為registry,對應RegistryProtocol這個擴充套件類;而如果
        //直連呼叫,getProtocol為空或者是指定的協議(一般為dubbo協議),對應擴充套件類DubboProtocol。
        if (urls.size() == 1) {
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        } else {
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            for (URL url : urls) {
                invokers.add(refprotocol.refer(interfaceClass, url));
                if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                    registryURL = url; // use last registry url
                }
            }
            if (registryURL != null) { // registry url is available
                // use AvailableCluster only when register's cluster is available
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                invoker = cluster.join(new StaticDirectory(u, invokers));
            } else { // not a registry 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
    }
    //檢查invoker是否可用,最終呼叫的是Curator客戶端的getZookeeperClient().isConnected()方法
    if (c && !invoker.isAvailable()) {
        throw new IllegalStateException("Failed to check the status of the service ...");
    }
    //...省略日誌列印程式碼
    
    //建立代理物件,proxyFactory是SPI介面ProxyFactory的自適應擴充套件類,通過ProxyFactory的定義可知,
    //在未設定URL的proxy屬性時,獲取到預設的擴充套件類JavassistProxyFactory,但是ProxyFactory介面擁有
    //一個包裝擴充套件類StubProxyFactoryWrapper,因此實際獲取到的是StubProxyFactoryWrapper例項,並呼叫
    //它的getProxy方法
    return (T) proxyFactory.getProxy(invoker);
}

 

3.2.註冊中心協議:RegistryProtocol


  該Protocol的擴充套件實現類,根據承載了註冊中心資訊的URL以及服務介面型別建立服務引用Invoker,建立方法為refer。
/**
 * 獲取服務引用的I
 */
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    //從url的parameters欄位中獲取key=registry即註冊中心的協議頭,如zookeeper,
    //在將其設定為protocol,之後移除paramters欄位中registry這個key
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    
    //在獲取到RegistryProtocol這個擴充套件類例項時,dubbo SPI機制會自動裝配它的RegistryFactory欄位,
    //RegistryFactory是一個SPI介面,則實際裝配的是RegistryFactory的自適應擴充套件類。
    //另外,RegistryFactory的getRegistry方法被@Adaptive註解,且註解的value值為"protocol",
    //因此RegistryFactory自定義擴充套件類會呼叫方法引數URL的getProtocol,以其返回值作為實際擴充套件類的name。
    //一般我們使用的註冊中心為zookeeper,那麼最終會呼叫到ZookeeperRegistryFactory的getRegistry方法。
    Registry registry = registryFactory.getRegistry(url);
    
    //如果要獲取的服務引用為RegistryService,直接呼叫proxyFactory的getInvoker方法獲取Invoker
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    //將url中paremeters的key=refer的value查詢字串重新轉為Map
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    //獲取group配置
    String group = qs.get(Constants.GROUP_KEY);
    //若group不為空,並且有多個分組
    if (group != null && group.length() > 0) {
        if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
            //通過SPI載入MergeableCluster例項,並呼叫doRefer繼續執行獲取服務引用邏輯
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    //單group或無group,使用RegistryProtocol預設裝配的Cluster自適應擴充套件類呼叫doRefer方法
    return doRefer(cluster, registry, type, url);
}

/**
 * 建立Invoker物件
 */
 private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
     //建立RegistryDirectory服務目錄,服務目錄相關內容參考下一節,注意每次走到doRefer都會new。
     //維度是某個服務的(type)的在某個註冊中心(URL)的服務目錄
     RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
     directory.setRegistry(registry); //新增註冊中心屬性
     directory.setProtocol(protocol); //新增協議自適應擴充套件類
     
     //生成服務消費者URL,protocol=consumer,ip=register.ip的值,port=0,path=type的全限定名,格式:
     //consumer://192.168.54.1/com.alibaba.dubbo.rpc.service.GenericService?
     //application=monitor-app&check=false&dubbo=2.6.2&generic=true&
     //interface=com.bestpay.monitor.app.AlarmTestService&pid=6332&
     //protocol=dubbo&retries=-1&side=consumer&timeout=10000&timestamp=1561427241838
     Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());      
     URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
     
     //使用服務消費者URL進行註冊
     if (!Constants.ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(Constants.REGISTER_KEY, true)) {
         //呼叫Registry的register方法,一般為ZookeeperRegistry,它呼叫FailRegistry的register方法(見下)
         registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, 
                 Constants.CONSUMERS_CATEGORY,Constants.CHECK_KEY, String.valueOf(false)));
     }
     
     //向subscribeUrl的parameter引數新增鍵值對"category" -> "providers,configurators,routers"
     //訂閱服務提供方的zkNode下的providers、configurators、routers等節點資料
     //(1)將RegistryDirectory中的consumerUrl設定為subscribeUrl;
     //(2)呼叫Registry的subscribe方法,該方法:
     //(2.1)呼叫父類AbstractRegistry方法,向成員變數subscribed設定值;
     //(2.2)移除failedSubscribed、failedUnsubscribed、failedNotified該subscribeUrl相關資料
     //(3)如果訂閱失敗,則嘗試從ZookeeperRegistry初始化時從快取檔案讀取到的資料中獲取到URLs,
     //且如果URLs不為空,則向它們傳送訂閱失敗的通知;如果為空,且check=true,則直接丟擲異常;
     //否則將url加入failedSubscribed
     directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
                Constants.PROVIDERS_CATEGORY + "," + Constants.CONFIGURATORS_CATEGORY
                + "," + Constants.ROUTERS_CATEGORY));

     //呼叫SPI介面Cluster的自適應擴充套件類的join,根據Cluster定義可以知道自適應擴充套件類
     //應該獲取一個FailoverCluster例項,但是MockClusterWrapper是Cluster擴充套件類中的包裝類,
     //因此FailoverCluster會被包裝起來返回,最終自適應擴充套件類獲取到MockClusterWrapper例項。
     //呼叫擴充套件類MockClusterWrapper的join方法,該方法建立了一個MockClusterInvoker例項,
     //並維護了directory以及一個FailoverClusterInvoker。
     Invoker invoker = cluster.join(directory);
     
     //將服務引用invoker、registryUrl、consumerUrl、服務目錄directory包裝成ConsumerInvokerWrapper,
     //然後以serviceUniqueName = consumerUrl.getServiceKey()做為key,存入
     //在ProviderConsumerRegTable中的static變數ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>>
     ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
     return invoker;
 }

 

3.3.zookeeper註冊中心工廠:ZookeeperRegistryFactory


  註冊中心協議RegistryProtocol在判定註冊中心的協議為zookeeper後,會呼叫ZookeeperRegistryFactory的getRegistry方法來建立一個Zookeeper註冊中心物件ZookeeperRegistry。該Factory快取了所有zookeeper註冊中心的例項。
//dubbo的SPI機制決定了Dubbo執行過程中同一個擴充套件類例項只有一個,ZookeeperRegistryFactory中具有一個註冊中心的緩
//存,key為"zookeeper://172.17.45.14:2181/com.alibaba.dubbo.registry.RegistryService",即如下格式
//"protocol://username:password@ip:port/com.alibaba.dubbo.registry.RegistryService"。更多格式參考
//URL類的toServiceString方法
private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();

//例項化擴充套件類ZookeeperRegistryFactory時,會通過SPI的注入ZookeeperTransporter的自適應擴充套件類
private ZookeeperTransporter zookeeperTransporter;

/**
 * 獲取一個註冊中心封裝物件
 */
public Registry getRegistry(URL url) {
    //克隆url,並將Path設定為RegistryService的全限定名;
    //在URL的parameters引數中新增interface=RegistryService的全限定名鍵值對;
    //在URL的parameters引數中移除key=export、key=refer;
    url = url.setPath(RegistryService.class.getName())
        .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
        .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
    
    //獲取註冊中心Registry的快取key
    String key = url.toServiceString();
    LOCK.lock();
    try {
        //從快取中嘗試獲取
        Registry registry = REGISTRIES.get(key);
        if (registry != null) {
            return registry;
        }
        //如果沒有獲取,則呼叫建立方法建立快取
        registry = createRegistry(url);
        if (registry == null) {
            throw new IllegalStateException("Can not create registry " + url);
        }
        //存入快取
        REGISTRIES.put(key, registry);
        return registry;
    } finally {
        LOCK.unlock();
    }
}

/**
 * 建立快取,直接new一個ZookeeperRegistry物件
 */
public Registry createRegistry(URL url) {
    return new ZookeeperRegistry(url, zookeeperTransporter);
}

 

3.4.zookeeper註冊中心:ZookeeperRegistry


3.4.1.構造方法  


        ZookeeperRegistry為dubbo封裝的基於zookeeper的註冊中心,它並不為一個SPI介面,對於每個註冊中心都是通過new出來的例項,下面研究構造方法。
/**
 * ZookeeperRegistry的構造方法
 */
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    //呼叫父類FailbackRegistry的構造方法,方法見下
    super(url);
    //判斷url中的host欄位是否為"0.0.0.0"或者為表示anyhost的true
    if (url.isAnyHost()) {
        throw new IllegalStateException("registry address == null");
    }
    //獲取url中key為group的引數值,如果未獲取到,則給預設值"dubbo"
    String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
    //判斷group是否以"/"開頭,如果不是,則為其新增"/"字首
    if (!group.startsWith(Constants.PATH_SEPARATOR)) {
        group = Constants.PATH_SEPARATOR + group;
    }  
    this.root = group;
    
    //呼叫SPI介面ZookeeperTransporter的自適應擴充套件類,該擴充套件類為dubbo通過位元組碼技術生成。
    //connect方法被@Adaptive註解修飾,並且要求從key為client以及transporter中取值,如果
    //這兩個引數沒有值,則擴充套件類name取@SPI註解的value值"curator",一般來說都是這個值。
    //那麼自適應擴充套件類最終會呼叫CuratorZookeeperTransporter類的connect方法獲取Zk客戶端。
    zkClient = zookeeperTransporter.connect(url);
    
    //向獲取到的zkClient設定監聽器
    zkClient.addStateListener(new StateListener() {
        @Override
        public void stateChanged(int state) {
            if (state == RECONNECTED) {
                try {
                    //如果是RECONNECTED狀態,則進行恢復
                    recover();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    });
}

   FailbackRegistry為ZookeeperRegistry的父類,定義了註冊中心的重試邏輯:

//定時任務執行緒池,用於週期性的執行重試任務
private final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryFailedRetryTimer", true));

//定時任務執行緒執行結果
private final ScheduledFuture<?> retryFuture;

//需要重試的註冊失敗的URL
private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();

//需要重試的登出失敗的URL
private final Set<URL> failedUnregistered = new ConcurrentHashSet<URL>();

//需要重試的訂閱失敗的URL
private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();

//需要重試的解除訂閱失敗的URL
private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();

//需要重試的通知失敗的URL
private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();

/**
 * FailbackRegistry建構函式
 */
public FailbackRegistry(URL url) {
    //呼叫父類AbstractRegistry的構造方法,方法見下
    super(url);
    //獲取重試周期,預設5000毫秒
    int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
    //建立一個定時執行緒池,以retryPeriod為週期定時呼叫retry方法
    this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            try {
                //嘗試註冊失敗、解註冊失敗、訂閱失敗、解訂閱失敗、通知失敗的列表
                retry();
            } catch (Throwable t) { // Defensive fault tolerance
                logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
            }
        }
    }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
}

   AbstractRegistry為FailbackRegistry的父類,其定義了從快取檔案載入配置、快取配置到檔案、註冊、解註冊、訂閱、解除訂閱等Registry的主要功能。

//註冊URL列表
private final Set<URL> registered = new ConcurrentHashSet<URL>();

//訂閱URL列表
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();

//通知URL列表
private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<URL, Map<String, List<URL>>>();

/**
 * AbstractRegistry建構函式
 */
public AbstractRegistry(URL url) {
    //設定成員變數registryUrl=url
    setUrl(url);
       //獲取檔案的同步儲存標識
    syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);
     //獲取快取檔名,格式為"C:\Users\chenjunyi/.dubbo/dubbo-registry-monitor-app-172.17.45.14:2181.cache"
    String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(Constants.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 store file ...");
            }
        }
    }
    this.file = file;
    //讀取快取的配置檔案,將讀取結果存到Properties properties成員屬性中
    loadProperties();
    //拆分註冊中心URL中的address,將其backup的地址與主要地址拆成List<URL>,然後通知其監聽器
    notify(url.getBackupUrls());
}

 

3.4.2.register方法


  ZookeeperRegistry的register方法繼承自父類FailbackRegistry,它將服務消費/提供者的URL註冊到註冊中心上,並未重寫該方法:
/**
 * 繼承自父類FailbackRegistry的register方法,用於註冊服務引用(消費者)的URL
 */
public void register(URL url) {
    //呼叫父類AbstractRegistry的register方法
    super.register(url);
    failedRegistered.remove(url); //從註冊失敗列表中移除該url    
    failedUnregistered.remove(url); //從登出失敗列表中移除該url  
    try {
        //勾起實際的註冊方法
        doRegister(url);
    } catch (Exception e) {
        //如果丟擲異常
        Throwable t = e;
        //判斷check標識,從ZookeeperRegistry的registerUrl,即註冊中心URL的paramters獲取key=check的值,
        //從消費者URL,即url的parameters獲取key=check的值,以及獲取url的protocol。
        //計算flag check的布林值
        boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
            && url.getParameter(Constants.CHECK_KEY, true)
            && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
        //判斷異常型別是否為SkipFailbackWrapperException
        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);
        }

        //對於註冊失敗的消費者URL,新增到註冊失敗列表中Set<URL> failedRegistered
        failedRegistered.add(url);
    }
}

/**
 * 由ZookeeperRegistry實現的doRegister方法
 */
protected void doRegister(URL url) {
    try {
        //呼叫CuratorZookeeperClient建立節點,以泛化呼叫為例,傳入的引數URL格式為
        //consumer://192.168.54.1/com.alibaba.dubbo.rpc.service.GenericService?
        //application=monitor-app&category=consumers&check=false&dubbo=2.6.2&
        //generic=true&interface=com.bestpay.monitor.app.AlarmTestService&pid=19616&
        //protocol=dubbo&retries=-1&side=consumer&timeout=10000&timestamp=1561429026207
        //可以看出,它的path路徑GenericService不一定等於interface
        //(1)toUrlPath(url)獲取需要建立節點路徑,以消費者為例,其格式為
        //"/dubbo/com.bestpay.monitor.app.AlarmTestService/consumers/consumer%3A%2F%2F192.168.54.1%2F
        //com.alibaba.dubbo.rpc.service.GenericService%3Fapplication%3Dmonitor-app%26..."
        //可以看出,真正的建立節點路徑是interface介面作為path的路徑
        //(2)url.getParameter(Constants.DYNAMIC_KEY, true)決定是否建立臨時節點,true-臨時節點。
        //而CuratorZookeeperClient內部的create邏輯為:
        //(1)擷取示例中的"/dubbo/com.bestpay.monitor.app.AlarmTestService/consumers"作為
        //zkNode的父路徑並一級級建立父節點consumers,父節點都為永久節點;
        //(2)根據第二個引數的值決定建立的每個消費者節點(即擷取父路徑後遺留的字串)是否為臨時節點,
        //true-臨時節點;false-永久節點;        
        zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
    } catch (Throwable e) {
        throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

 

3.4.3.subscribe方法


  ZookeeperRegistry的subscribe方法繼承自父類FailbackRegistry,它訂閱服務提供者ZK節點下的routers節點、configurators節點以及providers節點,並未重寫該方法。該方法在實際的呼叫過程中,被RegistryDirectory的subscribe勾起:
/**
 * RegistryDirectory的subscribe方法
 */
public void subscribe(URL url) {
    setConsumerUrl(url); //簡單的設定RegistryDirectory成員變數consumerUrl
    //勾起成員變數Registry的subscribe方法,並將自身作為NotifyListener傳入
    registry.subscribe(url, this); 
}

   調起繼承自FailbackRegistry的subscribe方法:

/**
 * 繼承自FailbackRegistry的subscribe方法
 */ 
public void subscribe(URL url, NotifyListener listener) {
    //呼叫父類的訂閱方法,
    super.subscribe(url, listener);
    //從failedSubscribed、failedUnsubscribed、failedNotified列表中移除該url對應的listener
    removeFailedSubscribed(url, listener);
    try {
        //呼叫由ZookeeperRegistry實現的doSubscribe方法
        doSubscribe(url, listener);
    } catch (Exception e) {
        //如果doSubscribe執行丟擲異常
        Throwable t = e;
        //從載入的檔案快取Properties中獲取url.getServiceKey對應的快取
        List<URL> urls = getCacheUrls(url);
        if (urls != null && !urls.isEmpty()) {
            //如果讀取到的快取不為空,則對該url進行通知,通知的內容是檔案快取的內容
            notify(url, listener, urls);
            logger.error("Failed to subscribe ... Using cached list: ...");
        } else {
            //獲取check標識,getUrl方法獲取的是ZookeeperRegistry維護的registryUrl,
            //而url指的是服務消費者的URL,從它們的parameters欄位獲取check這個key的value;
            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 ... cause: ");
            } else {
                logger.error("Failed to subscribe url ... waiting for retry ...");
            }
        }
        //向成員變數 ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed新增訂閱失敗的資料
        addFailedSubscribed(url, listener);
    }
}

/**
 * 由ZookeeperRegistry實現的doSubscribe方法
 */
protected void doSubscribe(final URL url, final NotifyListener listener) {
    try {
        if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
            //如果方法引數URL的paramters中key=interface為*,暫時先不討論
            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() {
                    @Override
                    public void childChanged(String parentPath, List<String> currentChilds) {
                        for (String child : currentChilds) {
                            child = URL.decode(child);
                            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.isEmpty()) {
                for (String service : services) {
                    service = URL.decode(service);
                    anyServices.add(service);
                    subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
                                                                 Constants.CHECK_KEY, String.valueOf(false)), listener);
                }
            }
        } else {
            //方法引數URL的paramters中key=interface不為*
            List<URL> urls = new ArrayList<URL>();
            //通過toCategoriesPath方法生成要訂閱的ZK節點路徑,以interface=AlarmTestService為例:
            //(1)/dubbo/com.bestpay.monitor.app.AlarmTestService/providers;
            //(2)/dubbo/com.bestpay.monitor.app.AlarmTestService/configurators;
            //(3)/dubbo/com.bestpay.monitor.app.AlarmTestService/routers;
            //然後遍歷這3個路徑
            for (String path : toCategoriesPath(url)) {
                //獲取這個ZookeeperRegistry中的服務消費者URL對應的監聽器Map
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                //獲取NotifyListener,即RegistryDirectory對應的ChildListener。
                //一般來說RegistryDirectory與註冊中心Registry和服務引用介面(如GenericService)繫結。
                ChildListener zkListener = listeners.get(listener);
                if (zkListener == null) {
                    //如果沒有ChildListener,則建立一個並設定到listeners這個map中
                    listeners.putIfAbsent(listener, new ChildListener() {
                        @Override
                        public void childChanged(String parentPath, List<String> currentChilds) {
                            ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                        }
                    });
                    zkListener = listeners.get(listener);
                }
                //建立"XXXX/XXX/providers、configurators、providers"永久節點
                zkClient.create(path, false);
                //向Dubbo封裝的ZkClient新增ChildListener
                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);
    }
}

   類似於register方法,FallbackRegistry首先呼叫父類AbstractRegistry的subscribe,在經過一系列的校驗之後,向成員變數ConcurrentMap<URL, Set<NotifyListener>> subscribed新增服務引用(即消費者)的URL和監聽器。

/**
 * AbstractRegistry的subscribe方法
 */
public void subscribe(URL url, NotifyListener listener) {
    if (url == null) {
        throw new IllegalArgumentException("subscribe url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("subscribe listener == null");
    }
    if (logger.isInfoEnabled()) {
        logger.info("Subscribe: " + url);
    }
    Set<NotifyListener> listeners = subscribed.get(url);
    if (listeners == null) {
        subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
        listeners = subscribed.get(url);
    }
    listeners.add(listener);
}

 

3.4.4.恢復與重試


  為了保障連線能夠重試與恢復,在ZookeeperRegistry中為CuratorZookeeperClient設定了狀態監聽器,對於Curator通知的連線RECONNECTED事件,會勾起一個recover方法;在FailbackRegistry的構造方法中,同時設定了一個定時執行緒,用於呼叫retry方法,該方法撈取註冊失敗、解註冊失敗、訂閱失敗、解訂閱失敗、通知失敗的URL列表並重試。下面來看看這兩個方法是如何工作的:
/**
 * 用於根據Curator客戶端推送的連線狀態,RECONNECTED進行恢復
 */
protected void recover() throws Exception {
    //獲取恢復的註冊中心的地址URL列表Set<URL>,getRegistered()獲取成員變數Set<URL>
    Set<URL> recoverRegistered = new HashSet<URL>(getRegistered());
    if (!recoverRegistered.isEmpty()) {
        if (logger.isInfoEnabled()) {
            logger.info("Recover register url " + recoverRegistered);
        }
        //將getRegistered()新增到成員變數Set<URL> failedRegistered中
        for (URL url : recoverRegistered) {
            failedRegistered.add(url);
        }
    }
    
    //獲取恢復的訂閱列表Map<URL, Set<NotifyListener>>,getSubscribed()獲取成員變數subscribed
    Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>
                                                                            (getSubscribed());
    if (!recoverSubscribed.isEmpty()) {
        if (logger.isInfoEnabled()) {
            logger.info("Recover subscribe url " + recoverSubscribed.keySet());
        }
        for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
            URL url = entry.getKey();
            //新增到成員變數failedSubscribed中
            for (NotifyListener listener : entry.getValue()) {
                addFailedSubscribed(url, listener);
            }
        }
    }
}

/**
 * 對於成員變數failedRegistered、failedUnregistered、failedSubscribed、
 * failedUnsubscribed、failedNotified進行重試,可以看到,如果重試成功則將
 * 其移出相應的重試列表,如果重試失敗,則忽略異常等待下次重試
 */
protected void retry() {
    if (!failedRegistered.isEmpty()) {
        Set<URL> failed = new HashSet<URL>(failedRegistered);
        if (failed.size() > 0) {
            if (logger.isInfoEnabled()) {
                logger.info("Retry register " + failed);
            }
            try {
                for (URL url : failed) {
                    try {
                        doRegister(url);
                        failedRegistered.remove(url);
                    } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                        logger.warn("Failed to retry register ... waiting for again, cause: ...");
                    }
                }
            } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                logger.warn("Failed to retry register ... waiting for again, cause: ...");
            }
        }
    }
    if (!failedUnregistered.isEmpty()) {
        Set<URL> failed = new HashSet<URL>(failedUnregistered);
        if (!failed.isEmpty()) {
            if (logger.isInfoEnabled()) {
                logger.info("Retry unregister " + failed);
            }
            try {
                for (URL url : failed) {
                    try {
                        doUnregister(url);
                        failedUnregistered.remove(url);
                    } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                        logger.warn("Failed to retry unregister ... waiting for again, cause: ...");
                    }
                }
            } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                logger.warn("Failed to retry unregister ... waiting for again, cause: ");
            }
        }
    }
    if (!failedSubscribed.isEmpty()) {
        Map<URL, Set<NotifyListener>> failed = new HashMap<URL, Set<NotifyListener>>(failedSubscribed);
        for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>
                                                         (failed).entrySet()) {
            if (entry.getValue() == null || entry.getValue().size() == 0) {
                failed.remove(entry.getKey());
            }
        }
        if (failed.size() > 0) {
            if (logger.isInfoEnabled()) {
                logger.info("Retry subscribe " + failed);
            }
            try {
                for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
                    URL url = entry.getKey();
                    Set<NotifyListener> listeners = entry.getValue();
                    for (NotifyListener listener : listeners) {
                        try {
                            doSubscribe(url, listener);
                            listeners.remove(listener);
                        } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                            logger.warn("Failed to retry subscribe ... waiting for again, cause: ...");
                        }
                    }
                }
            } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                logger.warn("Failed to retry subscribe ... waiting for again, cause: ");
            }
        }
    }
    if (!failedUnsubscribed.isEmpty()) {
        Map<URL, Set<NotifyListener>> failed = new HashMap<URL, Set<NotifyListener>>
                                                                (failedUnsubscribed);
        for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>
                                                         (failed).entrySet()) {
            if (entry.getValue() == null || entry.getValue().isEmpty()) {
                failed.remove(entry.getKey());
            }
        }
        if (failed.size() > 0) {
            if (logger.isInfoEnabled()) {
                logger.info("Retry unsubscribe " + failed);
            }
            try {
                for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
                    URL url = entry.getKey();
                    Set<NotifyListener> listeners = entry.getValue();
                    for (NotifyListener listener : listeners) {
                        try {
                            doUnsubscribe(url, listener);
                            listeners.remove(listener);
                        } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                            logger.warn("Failed to retry unsubscribe ... waiting for again, cause: ..");
                        }
                    }
                }
            } catch (Throwable t) { 
                logger.warn("Failed to retry unsubscribe ... waiting for again, cause: ...");
            }
        }
    }
    if (!failedNotified.isEmpty()) {
        Map<URL, Map<NotifyListener, List<URL>>> failed = new HashMap<URL, Map<NotifyListener, 
                                                    List<URL>>>(failedNotified);
        for (Map.Entry<URL, Map<NotifyListener, List<URL>>> entry : new HashMap<URL, Map<NotifyListener, 
                                                     List<URL>>>(failed).entrySet()) {
            if (entry.getValue() == null || entry.getValue().size() == 0) {
                failed.remove(entry.getKey());
            }
        }
        if (failed.size() > 0) {
            if (logger.isInfoEnabled()) {
                logger.info("Retry notify " + failed);
            }
            try {
                for (Map<NotifyListener, List<URL>> values : failed.values()) {
                    for (Map.Entry<NotifyListener, List<URL>> entry : values.entrySet()) {
                        try {
                            NotifyListener listener = entry.getKey();
                            List<URL> urls = entry.getValue();
                            listener.notify(urls);
                            values.remove(listener);
                        } catch (Throwable t) { 
                            logger.warn("Failed to retry notify ... waiting for again, cause: ...");
                        }
                    }
                }
            } catch (Throwable t) { 
                logger.warn("Failed to retry notify ... waiting for again, cause: ...");
            }
        }
    }
}

 

3.5.zookeeper封裝客戶端:CuratorZookeeperClient


  CuratorZookeeperTransporter的connect方法直接new一個Dubbo封裝的CuratorZookeeperClient。
public ZookeeperClient connect(URL url) {
    return new CuratorZookeeperClient(url);
}

   而CuratorZookeeperClient是dubbo通過Curator封裝出來的zookeeper客戶端。它的建構函式通過Curator框架建立一個client,並且向該client新增一個連線狀態的監聽器。當有連線狀態改變時,會向CuratorZookeeperClient維護的StateListener呼叫stateChanged方法,傳入獲取到的狀態。

 public CuratorZookeeperClient(URL url) {
     super(url);
     try {
         CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
             .connectString(url.getBackupAddress())
             .retryPolicy(new RetryNTimes(1, 1000))
             .connectionTimeoutMs(5000);
         String authority = url.getAuthority();
         if (authority != null && authority.length() > 0) {
             builder = builder.authorization("digest", authority.getBytes());
         }
         client = builder.build();
         client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
             @Override
             public void stateChanged(CuratorFramework client, ConnectionState state) {
                 //回撥自身維護的監聽器List<StateListener>
                 if (state == ConnectionState.LOST) {
                     CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
                 } else if (state == ConnectionState.CONNECTED) {
                     CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
                 } else if (state == ConnectionState.RECONNECTED) {
                     CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
                 }
             }
         });
         client.start();
     } catch (Exception e) {
         throw new IllegalStateException(e.getMessage(), e);
     }
 }

 

3.6.StubProxyFactoryWrapper


  看看getProxy方法:
/** 
 * StubProxyFactoryWrapper的getProxy方法
 */
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
    //它首先呼叫包裝的JavassistProxyFactory的getProxy方法
    T proxy = proxyFactory.getProxy(invoker);
    if (GenericService.class != invoker.getInterface()) {
        //如果設定了本地代理類,則將獲取到的Proxy包裝為代理類物件
        String stub = invoker.getUrl().getParameter(
            Constants.STUB_KEY, invoker.getUrl().getParameter(Constants.LOCAL_KEY));
        if (ConfigUtils.isNotEmpty(stub)) {
            Class<?> serviceType = invoker.getInterface();
            if (ConfigUtils.isDefault(stub)) {
                if (invoker.getUrl().hasParameter(Constants.STUB_KEY)) {
                    stub = serviceType.getName() + "Stub";
                } else {
                    stub = serviceType.getName() + "Local";
                }
            }
            try {
                Class<?> stubClass = ReflectUtils.forName(stub);
                if (!serviceType.isAssignableFrom(stubClass)) {
                    throw new IllegalStateException("The stub implementation class ...");
                }
                try {
                    Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
                    proxy = (T) constructor.newInstance(new Object[]{proxy});
                    //export stub service
                    URL url = invoker.getUrl();
                    if (url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT)) {
                        url = url.addParameter(Constants.STUB_EVENT_METHODS_KEY, StringUtils.join(
                            Wrapper.getWrapper(proxy.getClass()).getDeclaredMethodNames(), ","));
                        url = url.addParameter(Constants.IS_SERVER_KEY, Boolean.FALSE.toString());
                        try {
                            export(proxy, (Class) invoker.getInterface(), url);
                        } catch (Exception e) {
                            LOGGER.error("export a stub service error.", e);
                        }
                    }
                } catch (NoSuchMethodException e) {
                    throw new IllegalStateException("No such constructor \"public ...");
                }
            } catch (Throwable t) {
                LOGGER.error("Failed to create stub implementation class ...");
            }
        }
    }
    return proxy;
}

/**
 * JavassistProxyFactory的getProxy方法,繼承自AbstractProxyFactory
 */
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
    Class<?>[] interfaces = null;
    //獲取介面列表
    String config = invoker.getUrl().getParameter("interfaces");
    if (config != null && config.length() > 0) {
        //切分介面列表
        String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
        if (types != null && types.length > 0) {
             //設定服務介面類和EchoService.class到interfaces中
            interfaces = new Class<?>[types.length + 2];
            interfaces[0] = invoker.getInterface();
            interfaces[1] = EchoService.class;
            for (int i = 0; i < types.length; i++) {
                interfaces[i + 1] = ReflectUtils.forName(types[i]);
            }
        }
    }
    //如果介面列表為空,則設定它為服務介面以及回聲測試介面
    if (interfaces == null) {
        interfaces = new Class<?>[]{invoker.getInterface(), EchoService.class};
    }
    
    return getProxy(invoker, interfaces);
}

/**
 * JavassistProxyFactory實現的getProxy方法,非繼承,獲取服務介面代理類物件
 */
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    //通過位元組碼技術生成類Proxy的子類(Proxy是抽象類)
    //並呼叫Proxy子類的newInstance方法建立服務介面代理類例項,該例項維護一個InvokerInvocationHandler
    //代理類例項的每個方法實現都會呼叫InvokerInvocationHandler的invoke方法,將服務介面的方法引數以及
    //呼叫的方法反射物件Method傳入。InvokerInvocationHandler的invoke方法在一系列檢查後最終執行如下方法:
    //return invoker.invoke(new RpcInvocation(method, args)).recreate();
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

 

3.7.Proxy


  Proxy是一個抽象類,該類提供一個Proxy方法,該方法通過位元組碼技術生成Proxy的之類。該子類有一個方法,用於生成Invoker代理類。
//Proxy子類例項快取
private static final Map<ClassLoader, Map<String, Object>> ProxyCacheMap = 
    new WeakHashMap<ClassLoader, Map<String, Object>>();

/**
 * 生成Proxy子類,該子類封裝了生成服務介面代理類的邏輯
 */
public static Proxy getProxy(Class<?>... ics) {
    return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);
}

/**
 * 生成Proxy子類,該子類封裝了生成服務介面代理類的邏輯
 */
public static Proxy getProxy(ClassLoader cl, Class<?>... ics) {
    if (ics.length > 65535)
        throw new IllegalArgumentException("interface limit exceeded");

    StringBuilder sb = new StringBuilder();
    //遍歷介面列表
    for (int i = 0; i < ics.length; i++) {
        String itf = ics[i].getName();
        //檢測是否為介面型別
        if (!ics[i].isInterface())
            throw new RuntimeException(itf + " is not a interface.");
        //使用提供的ClassLoader,即Proxy自身的ClassLoader載入當前遍歷的介面
        Class<?> tmp = null;
        try {
            tmp = Class.forName(itf, false, cl);
        } catch (ClassNotFoundException e) {
        }
        //檢測介面是否相同,這裡相當於判斷Proxy的類載入器與介面的類載入器是否為一個
        if (tmp != ics[i])
            throw new IllegalArgumentException(ics[i] + " is not visible from class loader");
        //拼接介面名稱,格式"介面名1;介面名2..."
        sb.append(itf).append(';');
    }

    //獲取類載入器對應的Proxy快取,一個Map物件
    Map<String, Object> cache;
    synchronized (ProxyCacheMap) {
        //獲取當前類載入器的Proxy例項快取,key為ClassLoader
        cache = ProxyCacheMap.get(cl);
        if (cache == null) {
            cache = new HashMap<String, Object>();
            //快取為null,設定該ClassLoader的快取
            ProxyCacheMap.put(cl, cache);
        }
    }
    
    //以介面名作為key,從cache中獲取這個key的Proxy例項快取
    String key = sb.toString();
    Proxy proxy = null;
    //獲取value時的併發控制,使用監視器鎖
    synchronized (cache) {
        do {
            //如果value就是應用型別包裝的,直接從引用中獲取例項
            Object value = cache.get(key);
            if (value instanceof Reference<?>) {
                proxy = (Proxy) ((Reference<?>) value).get();
                if (proxy != null)
                    return proxy;
            }
            //如果value不是引用型別,則進行判斷其是否等於PendingGenerationMarker,即一個Object物件。
            //這是使用了cache.wait(),讓其他執行緒在cache這個物件上進行等待,原因如下:
            //(1)首先一個在cache中未命中的key其value肯定為null;那麼我們肯定要建立這個value;
            //(2)既然value==null,則進入到else邏輯,設定一個key的標識,跳出迴圈,也跳出監視器鎖同步塊;
            //(3)當前執行緒程式碼繼續執行去建立Proxy的例項,其他執行緒進入到這個監視器鎖塊,就會進行迴圈獲取Proxy;
            //(4)不斷地迴圈獲取滿足條件的Reference也沒錯,但是這樣不斷瘋狂的迴圈,對程式有影響;
            //(5)因此,設定PendingGenerationMarker的目的也在於此,作為一個標識,如果發現key的value還是它,
            //   就表示Proxy例項尚未建立完成,在此進行等待;直到例項建立完成並進行notify。
            //(6)當然,若使用監視器鎖將建立Proxy的程式碼鎖住也可以,但是這樣鎖住的程式碼塊太大了。
            if (value == PendingGenerationMarker) {
                try {
                    cache.wait();
                } catch (InterruptedException e) {
                }
            } else {
                cache.put(key, PendingGenerationMarker);
                break;
            }
        }
        while (true);
    }

    //原子計數器+1,作為id,用於拼接生成的Proxy子類的名字
    long id = PROXY_CLASS_COUNTER.getAndIncrement();
    String pkg = null;
    //ccm用於為Proxy生成子類,ccp為服務介面生成代理類
    ClassGenerator ccp = null, ccm = null;
    try {
        /************************* 開始建立服務介面代理類 *************************/
        //建立生成服務介面代理類的ClassGenerator
        ccp = ClassGenerator.newInstance(cl);

        Set<String> worked = new HashSet<String>();
        List<Method> methods = new ArrayList<Method>();
        //遍歷要代理的介面
        for (int i = 0; i < ics.length; i++) {
            //檢測介面訪問級別是否為public
            if (!Modifier.isPublic(ics[i].getModifiers())) {
                //不為public級別的介面,需要確保它們必須在同一個包下
                String npkg = ics[i].getPackage().getName();
                if (pkg == null) {
                    pkg = npkg;
                } else {
                    if (!pkg.equals(npkg))
                        throw new IllegalArgumentException("non-public interfaces from diff pack...");
                }
            }
            //新增介面到cpp中
            ccp.addInterface(ics[i]);
            //遍歷服務介面的所有方法
            for (Method method : ics[i].getMethods()) {
                //獲取方法描述,JVM格式"realTimePushList(Lcom/bestpay/messagecenter/product/
                //service/api/dto/push/RealTimePushListDTO;)Lcom/bestpay/dubbo/result/Result;"
                String desc = ReflectUtils.getDesc(method);
                //如果方法描述字串已在worked中,則忽略。考慮A介面和B介面中包含一個完全相同的方法的情況
                if (worked.contains(desc))
                    continue;
                worked.add(desc);

                //服務介面代理類方法大小 TODO 
                int ix = methods.size();
                Class<?> rt = method.getReturnType();
                Class<?>[] pts = method.getParameterTypes();

                //拼接程式碼字串"Object[] args = new Object[N];",N是當前遍歷的method引數個數
                StringBuilder code = new StringBuilder("Object[] args = new Object[").
                                    append(pts.length).append("];");
                //遍歷method的引數列表
                for (int j = 0; j < pts.length; j++)
                    //拼接程式碼字串"args[j]=($w)$k;",其中k=j+1。這個是args的賦值語句
                    code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
                //拼接handler的呼叫語句,"Object ret = handler.invoke(this, methods[ix], args);"
                //handler是java動態代理InvocationHandler的一個實現類,ix為methods.size()。             
                code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");
                //若方法返回型別不為void,則拼接返回型別並進行強制型別轉換"return (型別)ret;"
                if (!Void.TYPE.equals(rt))
                    code.append(" return ").append(asArgument(rt, "ret")).append(";");
                //向List<Method>中新增該method
                methods.add(method);
                //新增方法名、訪問控制符、返回型別、引數列表、丟擲異常型別、方法體程式碼到ClassGenerator中 
                ccp.addMethod(method.getName(), method.getModifiers(), rt, pts,
                              method.getExceptionTypes(), code.toString());
            }
        }
        //設定服務介面代理類包名就為Proxy類所在的包名
        if (pkg == null)
            pkg = PACKAGE_NAME;
        //設定服務介面代理類類名為"pkg + ".proxy" + id",比如org.apache.dubbo.proxy0,注意是小寫
        String pcn = pkg + ".proxy" + id;
        ccp.setClassName(pcn);
        //新增成員屬性Method[] methods
        ccp.addField("public static java.lang.reflect.Method[] methods;");
        //新增成員屬性InvocationHandler
        ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
        //新增帶有InvocationHandler引數的構造方法,比如:
        //public proxy0(java.lang.reflect.InvocationHandler $1) { handler=$1; }
        ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, 
                           new Class<?>[0], "handler=$1;");
        //新增預設無參構造器
        ccp.addDefaultConstructor();
        //生成服務介面代理Class
        Class<?> clazz = ccp.toClass();
        //將服務介面代理類的static屬性methods設定為上面收集到的methods列表
        clazz.getField("methods").set(null, methods.toArray(new Method[0]));

        /************************* 開始建立Proxy類的子類 *************************/          
        String fcn = Proxy.class.getName() + id; //類名=Proxy全限定名+id,如Proxy1、Proxy2等      
        ccm = ClassGenerator.newInstance(cl); //建立生成Proxy子類的ClassGenerator       
        ccm.setClassName(fcn); //設定類名       
        ccm.addDefaultConstructor(); //新增預設構造器     
        ccm.setSuperClass(Proxy.class); //設定父類
        //新增方法newInstance,該方法呼叫構造方法,格式如下:
        //public Object newInstance(java.lang.reflect.InvocationHandler $1) { 
        //    return new org.apache.dubbo.proxy0($1);
        //}
        ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + 
                      " h){ return new " + pcn + "($1); }");
        Class<?> pc = ccm.toClass(); //生成Proxy子類Class
        proxy = (Proxy) pc.newInstance(); //生成Proxy子類的例項
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        //釋放資源
        if (ccp != null)
            ccp.release();
        if (ccm != null)
            ccm.release();
        //同步設定該Proxy子類的例項快取,使用弱引用
        synchronized (cache) {
            if (proxy == null)
                cache.remove(key);
            else
                cache.put(key, new WeakReference<Proxy>(proxy));
            //通知所有在cache上等待的執行緒
            cache.notifyAll();
        }
    }
    return proxy;
}

 

相關文章