Dubbo源碼學習12

該篇幅分析RegistryDirectory.subscribe(URL url)方法

RegistryDirectory.subscribe(URL url)

public void subscribe(URL url) {
        //consumer://169.254.22.149/com.alibaba.dubbo.study.day01.xml.service.EchoService?application=echo-consumer&
        //category=providers,configurators,routers&check=false&dubbo=2.0.2&echo.async=true&echo.retries=3&
        //interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=24092&
        //side=consumer&timestamp=1573453550695
        setConsumerUrl(url);
        //ZookeeperRegistry的subscribe調用,這一步操作會建立對zk的節點目錄providers、configurators、routers的監聽
        //如果這些zk的這些目錄內容發生改變則會反饋到RegistryDirectory的notify方法!!!
        //邏輯我就不用講了吧,同服務暴露的流程一致!
        registry.subscribe(url, this);
    }

首先設置consumerUrl屬性:consumer://169.254.22.149/com.alibaba.dubbo.study.day01.xml.service.EchoService?application=echo-consumer& category=providers,configurators,routers&check=false&dubbo=2.0.2&echo.async=true&echo.retries=3& interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=24092& side=consumer&timestamp=1573453550695,調用註冊中心subscribe(url,this)進行訂閱,由於RegistryDirectory實現了NotifyListener接口,所以當routers、providers、configurators節點下的數據發生變化將會觸發NotifyListener的notify(List<URL> urls)方法

FailbackRegistry.subscribe(URL url,NotifyListener)

 @Override
    public void subscribe(URL url, NotifyListener listener) {
        //將當前url對應的Listener放入緩存集合
        super.subscribe(url, listener);
        //從failedSubscribed/failedUnsubscribed集合中刪除該OverrideListener
        removeFailedSubscribed(url, listener);
        try {
            // Sending a subscription request to the server side
            //發送訂閱請求到服務器端
            doSubscribe(url, listener);
        } catch (Exception e) {
            Throwable t = e;
            //獲取該url對應的緩存url
            List<URL> urls = getCacheUrls(url);
            if (urls != null && !urls.isEmpty()) {
                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 {
                // If the startup detection is opened, the Exception is thrown directly.
                //如果開啓了check = true,直接拋出異常
                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);
                }
            }

            // Record a failed registration request to a failed list, retry regularly
            addFailedSubscribed(url, listener);
        }
    }

該方法我們在服務暴露已經分析過了,無妨重新分析一遍加深印象

AbstractRegistry.subscribe(url,listener)

@Override
    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);
        }
        //key爲overrideSuscribeUrl類似如下格式
        //listener
        // provider://169.254.22.149:20880/com.alibaba.dubbo.study.day01.xml.service.EchoService?anyhost=true&application=echo-provider&bean.name=com.alibaba.dubbo.study.day01.xml.service.EchoService&category=configurators&check=false&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo&pid=3720&side=provider&timestamp=1571881716824
        // consumer://169.254.22.149/com.alibaba.dubbo.study.day01.xml.service.EchoService?application=echo-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.2&echo.async=true&echo.retries=3&interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=15272&side=consumer&timestamp=1573623575474
        Set<NotifyListener> listeners = subscribed.get(url);
        if (listeners == null) {
            subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
            listeners = subscribed.get(url);
        }
        listeners.add(listener);
    }

將url與List<NotifyListener>關係通過map維護起來

FailbackRegistry.removeFailedSubscribed(URL url,NotifyListener)

private void removeFailedSubscribed(URL url, NotifyListener listener) {
        //從failedSubscribed/failedUnsubscribed中overrideSubscribeUrl所對應的監聽器集合中刪除overrideSubscribeListener實例
        Set<NotifyListener> listeners = failedSubscribed.get(url);
        if (listeners != null) {
            listeners.remove(listener);
        }
        //
        listeners = failedUnsubscribed.get(url);
        if (listeners != null) {
            listeners.remove(listener);
        }
        //從failedNotified中獲取overrideSubscrbieUrl通知失敗的map Map<NotifyListener, List<URL>>
        //然後移除listener
        Map<NotifyListener, List<URL>> notified = failedNotified.get(url);
        if (notified != null) {
            notified.remove(listener);
        }
    }

從failedSubscribed/failedUnsubscribed map集合中移除url(consumer://169.254.22.149/com.alibaba.dubbo.study.day01.xml.service.EchoService?application=echo-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.2&echo.async=true&echo.retries=3&interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=15272&side=consumer&timestamp=1573623575474)對應的NotifyListener,從failedNotified map中移除url對應的Map<NotifyListener,List<URL>>的notifyListener對應的List<URL>

ZookeeperRegistry.doSubscribe(final URL url,final NotifyListener listener)

@Override
    protected void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            //訂閱所有數據,監控中心的訂閱
            if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
                //獲取根目錄
                String root = toRootPath();
                //獲取url對應的監聽器集合
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                if (listeners == null) {
                    //不存在,賦值新的
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                //獲取listener的監聽器
                ChildListener zkListener = listeners.get(listener);
                //zkListener爲null說明需要創建新的
                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);
                                    //訂閱URL
                                    subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
                                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                                }
                            }
                        }
                    });
                    //再次獲取listener對應的ChildListener
                    zkListener = listeners.get(listener);
                }
                //創建root永久節點
                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 {
                /**
                 * https://www.cnblogs.com/java-zhao/p/7632929.html 參考分析
                 */
                //處理消費者的訂閱請求
                List<URL> urls = new ArrayList<URL>();
                //獲取url的所有類型後

                //url(overrideSubscribeUrl):provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?
                // anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&
                // interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9544&side=provider&timestamp=1507643800076
                for (String path : toCategoriesPath(url)) {
                    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) {
                                ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                            }
                        });
                        zkListener = listeners.get(listener);
                    }
                    //創建持久化節點創建持久化節點/dubbo/com.alibaba.dubbo.demo.DemoService/configurators
                    zkClient.create(path, false);
                    //監聽/dubbo/com.alibaba.dubbo.demo.DemoService/configurators節點
                    //返回path的子節點
                    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);
        }
    }

首先調用通過toCategoriesPath(URL url)獲取到zk需要監聽的節點,然後創建url: :consumer://169.254.22.149/com.alibaba.dubbo.study.day01.xml.service.EchoService?application=echo-consumer& category=providers,configurators,routers&check=false&dubbo=2.0.2&echo.async=true&echo.retries=3& interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=22152& side=consumer&timestamp=1573625041767對應的NotifyListener的ChildListener,ConcurrentMap<NotifyListener, ChildListener>,調用zkClient的create方法創建path:/dubbo/com.alibaba.dubbo.study.day01.xml.service.EchoService/providers的臨時節點,調用zkClient的addChildListener方法將path與zkListener節點關聯起來,這樣子當path節點數據發生變化,zkListener就能立馬監聽到了,zkListener監聽到了會調用childChanged方法,然後NotifyListener就能感知到節點數據變化,進而發生了動態訂閱。此時的urls列表可能類似這樣子

toCategoriesPath(URL url)

private String[] toCategoriesPath(URL url) {
        String[] categories;
        //代表所有類別
        if (Constants.ANY_VALUE.equals(url.getParameter(Constants.CATEGORY_KEY))) {
            categories = new String[]{Constants.PROVIDERS_CATEGORY, Constants.CONSUMERS_CATEGORY,
                    Constants.ROUTERS_CATEGORY, Constants.CONFIGURATORS_CATEGORY};
        } else {
            //獲取url的category屬性,沒有則給定provider類型
            categories = url.getParameter(Constants.CATEGORY_KEY, new String[]{Constants.DEFAULT_CATEGORY});
        }
        String[] paths = new String[categories.length];
        for (int i = 0; i < categories.length; i++) {
            paths[i] = toServicePath(url) + Constants.PATH_SEPARATOR + categories[i];///dubbo/com.alibaba.dubbo.demo.DemoService/configurators
        }
        return paths;
    }

對於consumer://xxx來說該方法會返回的path列表有三個分別類似

  • 1./dubbo/com.alibaba.dubbo.study.day01.xml.service.EchoService/providers
  • 2./dubbo/com.alibaba.dubbo.study.day01.xml.service.EchoService/configurators
  • 3./dubbo/com.alibaba.dubbo.study.day01.xml.service.EchoService/routers

AbstractZookeeperClient.addChildListener(String path, final ChildListener listener)

 /**
     * 根據path從ConcurrentMap<String, ConcurrentMap<ChildListener, TargetChildListener>> childListeners
     * 獲取ConcurrentMap<ChildListener, TargetChildListener>,沒有就創建
     *
     * 根據ChildListener獲取TargetChildListener,沒有就創建,TargetChildListener是真正的監聽path的子節點變化的監聽器
     * createTargetChildListener(String path, final ChildListener listener):創建一個真正的用來執行當path節點的子節點發生變化時的邏輯
     *
     * addTargetChildListener(path, targetListener):將剛剛創建出來的子節點監聽器訂閱path的變化,這樣之後,path的子節點發生了變化時
     * ,TargetChildListener纔會執行相應的邏輯。而實際上TargetChildListener又會調用ChildListener的實現類的childChanged(String parentPath,
     * List<String> currentChilds)方法,而該實現類,正好是ZookeeperRegistry中實現的匿名內部類,
     * 在該匿名內部類的childChanged(String parentPath, List<String> currentChilds)方法中,
     * 調用了ZookeeperRegistry.notify(URL url, NotifyListener listener, List<URL> urls)
     *
     */
    @Override
    public List<String> addChildListener(String path, final ChildListener listener) {
        ConcurrentMap<ChildListener, TargetChildListener> listeners = childListeners.get(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);
    }

上面代碼首先獲取path:/dubbo/com.alibaba.dubbo.study.day01.xml.service.EchoService/providers的ChildListener的TargetChildListener,如果沒有調用createTargetChildListener方法創建並加入到childListeners緩存起來ConcurrentHashMap<ChildListener, TargetChildListener>();TargetChildListener方法將會委託childListner實現節點數據變化進行處理的邏輯,最後調用addTargetChildListener真正的將zk自己的TargetChildListener監聽path的數據並返回path下的子節點數據

ChildListener.childChaged(String parentPath, List<String> currentChilds)

@Override
                            public void childChanged(String parentPath, List<String> currentChilds) {
                                ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                            }

監聽到path節點數據變化委託ZookeeperRegistry.notify方法進行處理

FailbackRegistry.notify(URL url, NotifyListener listener, List<URL> urls)

@Override
    protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        if (url == null) {
            throw new IllegalArgumentException("notify url == null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("notify listener == null");
        }
        try {
            doNotify(url, listener, urls);
        } catch (Exception t) {
            // Record a failed registration request to a failed list, retry regularly
            // 將失敗的通知請求記錄到失敗列表,定時重試
            Map<NotifyListener, List<URL>> listeners = failedNotified.get(url);
            if (listeners == null) {
                failedNotified.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, List<URL>>());
                listeners = failedNotified.get(url);
            }
            listeners.put(listener, urls);
            logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
        }
    }

上述方法在異常中會將notify失敗的放到failedNotified中重試

AbstractRegistry.notify(URL url, NotifyListener listener, List<URL> urls)

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        if (url == null) {
            throw new IllegalArgumentException("notify url == null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("notify listener == null");
        }
        if ((urls == null || urls.isEmpty())
                && !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>>();
        //將匹配的urls按category分類保存到Map中
        //key-categroyName value-List<URL>
        for (URL u : urls) {
            if (UrlUtils.isMatch(url, u)) {
                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);
            }
        }
        if (result.size() == 0) {
            return;
        }
        //獲取url(overrideSubscribeUrl):provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?
        // anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&
        // interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9544&side=provider&timestamp=1507643800076
        //consumer://169.254.22.149/com.alibaba.dubbo.study.day01.xml.service.EchoService?application=echo-consumer&
        // category=providers,configurators,routers&check=false&dubbo=2.0.2&echo.async=true&echo.retries=3&
        // interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=15140&
        // side=consumer&timestamp=1573626718233
        //對應的通知過的URL集合 key-種類 value-List<URL> urls
        Map<String, List<URL>> categoryNotified = notified.get(url);
        if (categoryNotified == null) {
            notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
            categoryNotified = notified.get(url);
        }
        //遍歷上述方法創建的map key-categroyName value-List<URL>集合
        for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
            //categroy
            String category = entry.getKey();
            //該分類對應的url
            List<URL> categoryList = entry.getValue();
            //放入到map中
            categoryNotified.put(category, categoryList);
            //保存到本地磁盤緩存中
            saveProperties(url);
            //調用傳入的listener的notify方法(注意:這裏調用的正是文章開頭創建的overrideSubscribeListener實例的notify方法)
            listener.notify(categoryList);
        }
    }

首先將urls(上圖中的urls)按照category分類放到map中key-categroyName value-List<URL>,從notified中獲取url:consumer://169.254.22.149/com.alibaba.dubbo.study.day01.xml.service.EchoService?application=echo-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.2&echo.async=true&echo.retries=3&interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=15140&side=consumer&timestamp=1573626718233按照category(routers,configurators,providers)遍歷按category分別進行RegistryDirectory.notify的調用!當然還有通知過的數據到notified中map中key-categroyName value-List<URL>(上圖中的urls)到文件的邏輯。

RegistryDirectory.notify(List<URL> urls)

@Override
    public synchronized void notify(List<URL> urls) {
        //攜帶提供者元數據的url
        List<URL> invokerUrls = new ArrayList<URL>();
        //攜帶路由元數據的url
        List<URL> routerUrls = new ArrayList<URL>();
        //攜帶配置元數據的url
        List<URL> configuratorUrls = new ArrayList<URL>();
        //遍歷routers、configurators、providers目錄下的元數據配置
        for (URL url : urls) {
            String protocol = url.getProtocol();
            String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            //如果category類型爲routers 或者url的協議爲route
            if (Constants.ROUTERS_CATEGORY.equals(category)
                    || Constants.ROUTE_PROTOCOL.equals(protocol)) {
                routerUrls.add(url);
                //如果category類型爲configurators 或者 協議類型爲override
            } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
                    || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
                configuratorUrls.add(url);
                //如果category類型爲providers
            } 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());
            }
        }
        //
        //轉換configuratorsUrl爲List<Configurator>對象
        if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
            this.configurators = toConfigurators(configuratorUrls);
        }
        // routers
        //轉換routerUrls對象爲List<Router>
        if (routerUrls != null && !routerUrls.isEmpty()) {
            List<Router> routers = toRouters(routerUrls);
            if (routers != null) { // null - do nothing
                setRouters(routers);
            }
        }
        //
        List<Configurator> localConfigurators = this.configurators; // local reference
        // merge override parameters
        // 合併override的參數
        this.overrideDirectoryUrl = directoryUrl;
        if (localConfigurators != null && !localConfigurators.isEmpty()) {
            for (Configurator configurator : localConfigurators) {
                this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
            }
        }
        // providers
        refreshInvoker(invokerUrls);
    }

將urls按照category的分類分別保存到三個集合中invokerUrls,routerUrls,configuratorUrls;將configuratorUrls轉換爲List<Configurator> configurators集合作爲RegistryDirectory的成員變量屬性,將routeUrls轉換成List<Router> routers作爲RegistryDirectory的routers的成員變量屬性,使用configurators對overrdeDirectoryUrl:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=echo-consumer&check=false& dubbo=2.0.2&echo.async=true&echo.retries=3&interface=com.alibaba.dubbo.study.day01.xml.service.EchoService& methods=echo,addListener&pid=24924&register.ip=169.254.22.149&side=consumer&timestamp=1573453320189進行處理,最後調用 refreshInvoker 方法刷新 Invoker 列表

toConfigurators(List<URL> urls)

/**
     * Convert override urls to map for use when re-refer.
     * 將替代網址轉換爲映射以供重新引用時使用
     * Send all rules every time, the urls will be reassembled and calculated
     * 每次發送所有規則,網址將重新組合並計算
     *
     * @param urls Contract:
     *             </br>1.override://0.0.0.0/...( or override://ip:port...?anyhost=true)&para1=value1... 表示全局規則(所有提供者均生效)
     *             </br>2.override://ip:port...?anyhost=false 特殊規則(僅適用於特定提供者)
     *             </br>3.override:// 不支持規則...,需要由註冊表本身進行計算。
     *             </br>4.override://0.0.0.0/ 沒有參數意味着清除覆蓋
     * @return
     */
    public static List<Configurator> toConfigurators(List<URL> urls) {
        if (urls == null || urls.isEmpty()) {
            return Collections.emptyList();
        }

        List<Configurator> configurators = new ArrayList<Configurator>(urls.size());
        for (URL url : urls) {
            //如果是empty://清空configurators
            if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
                configurators.clear();
                break;
            }
            //獲取url中的所有參數
            Map<String, String> override = new HashMap<String, String>(url.getParameters());
            //The anyhost parameter of override may be added automatically, it can't change the judgement of changing url
            //override://xxx的any屬性可能會被自動添加,
            //刪除掉anyhost屬性後,沒有其他屬性了
            override.remove(Constants.ANYHOST_KEY);
            if (override.size() == 0) {
                //清空所有configurators,進入下一次循環
                configurators.clear();
                continue;
            }
            //添加
            configurators.add(configuratorFactory.getConfigurator(url));
        }
        Collections.sort(configurators);
        return configurators;
    }

上述方法將override://xxx轉換爲configurators列表,遍歷urls(override://xxx),如果是有empty://xxx則清空configurators配置列表並跳出循環意思就是清理掉所有的配置規則,獲取url(override://xxx)中的所有parameters,如果url只有anyhost屬性,清空configurators,進入下一輪循環,最後使用configuratorFactory.getConfigurator(url)創建Configurator對象(),ConfiguratoryFactory是dubbo的一個spi接口,在RegistryDirectory中其類型爲:ConfiguratorFactory$Adaptive 代碼如下

package com.alibaba.dubbo.rpc.cluster;

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

public class ConfiguratorFactory$Adaptive implements com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory {
    public com.alibaba.dubbo.rpc.cluster.Configurator getConfigurator(com.alibaba.dubbo.common.URL arg0) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getProtocol();
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory extension = (com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory.class).getExtension(extName);
        return extension.getConfigurator(arg0);
    }
}

一般我們採用override://xxx進行配置,採用覆蓋配置動態實現RPC的調用行爲

OverrideConfiguratorFactory.java

public class OverrideConfiguratorFactory implements ConfiguratorFactory {

    @Override
    public Configurator getConfigurator(URL url) {
        return new OverrideConfigurator(url);
    }

}

一般情況下我們採用OverrideConfigurator對原有的配置進行覆蓋,所以下文以OverrideConfigurator爲重點內容。

AbsentConfiguratorFactory.java

public class AbsentConfiguratorFactory implements ConfiguratorFactory {

    @Override
    public Configurator getConfigurator(URL url) {
        return new AbsentConfigurator(url);
    }

}

OverrideConfigurator.java

  • Configurator.java:配置providerUrl的抽象接口
public interface Configurator extends Comparable<Configurator> {

    /**
     * 獲取配置url
     * override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&disbaled=true
     * @return configurator url.
     */
    URL getUrl();

    /**
     * 配置提供者url
     * @param url - old provider url.
     * @return new provider url.
     */
    URL configure(URL url);

}
  • AbstractConfigurator.java,提供了對providerUrl配置的抽象實現,並提供模板方法doConfigure(URL currentUrl, URL configUrl)留給子類決定去覆蓋還是保留原有配置!
/**
     * 攜帶配置的Url
     * override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&disbaled=true
     */
    private final URL configuratorUrl;

    public AbstractConfigurator(URL url) {
        if (url == null) {
            throw new IllegalArgumentException("configurator url == null");
        }
        this.configuratorUrl = url;
    }
  • OverrideConfigurator.java:具體的配置策略實現,主要是用於覆蓋原有提供者的配置
public class OverrideConfigurator extends AbstractConfigurator {

    public OverrideConfigurator(URL url) {
        super(url);
    }

    @Override
    public URL doConfigure(URL currentUrl, URL configUrl) {
        return currentUrl.addParameters(configUrl.getParameters());
    }

}

RegistryDirectory.toRouters(List<URL> urls)

private List<Router> toRouters(List<URL> urls) {
        List<Router> routers = new ArrayList<Router>();
        if (urls == null || urls.isEmpty()) {
            return routers;
        }
        if (urls != null && !urls.isEmpty()) {
            for (URL url : urls) {
                //如果是empty:// 進入下一次循環
                if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
                    continue;
                }
                //獲取router屬性route://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=host != 10.20.153.10,10.20.153.11
                //支持scipt、condition、file三種類型
                String routerType = url.getParameter(Constants.ROUTER_KEY);
                if (routerType != null && routerType.length() > 0) {
                    url = url.setProtocol(routerType);
                }
                try {
                    //使用routerFactory創建Router並添加
                    Router router = routerFactory.getRouter(url);
                    if (!routers.contains(router))
                        routers.add(router);
                } catch (Throwable t) {
                    logger.error("convert router url to router error, url: " + url, t);
                }
            }
        }
        return routers;
    }

上述方法遍歷urls,首先判斷url是否empty://xxx是就跳過進入下一次循環,然後獲取route的種類,dubbo目前支持file、condition、file三種類型route配置,最後使用RegistryDirectory.routerFactory,創建router實例,存入結果集合routers中。在RegistryDirectory中其類型爲:ConfiguratorFactory$Adaptive 代碼如下

package com.alibaba.dubbo.rpc.cluster;

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

public class RouterFactory$Adaptive implements com.alibaba.dubbo.rpc.cluster.RouterFactory {
    public com.alibaba.dubbo.rpc.cluster.Router getRouter(com.alibaba.dubbo.common.URL arg0) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getProtocol();
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.RouterFactory) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.cluster.RouterFactory extension = (com.alibaba.dubbo.rpc.cluster.RouterFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.RouterFactory.class).getExtension(extName);
        return extension.getRouter(arg0);
    }
}

ConditionRouterFactory.java

public class ConditionRouterFactory implements RouterFactory {

    public static final String NAME = "condition";

    @Override
    public Router getRouter(URL url) {
        return new ConditionRouter(url);
    }

}

ScriptRouterFactory.java

public class ScriptRouterFactory implements RouterFactory {

    public static final String NAME = "script";

    @Override
    public Router getRouter(URL url) {
        return new ScriptRouter(url);
    }

}

Dubbo給我們提供了三種不同的router實現,它們分別是ConditionRouter、ScriptRouter、TagRouter,我們用的最多的還是ConditionRouter實現

ConditionRouter.java

  • Router.java:實現該接口主要負責按規則過濾出服務提供者的子集
public interface Router extends Comparable<Router>{

    /**
     *獲取router url
     * route://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11
     * @return url
     */
    URL getUrl();

    /**
     * 路由.
     * @param invokers
     * @param url        refer url
     * @param invocation
     * @return routed invokers
     * @throws RpcException
     */
    <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

    /**
     * 路由器的優先級,用於對路由器進行排序。
     *
     * @return router's priority
     */
    int getPriority();
  • AbstractRouter.java:Router接口的抽象實現類
/**
     * route://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11
     */
    protected URL url;
    /**
     * 路由優先級
     */
    protected int priority;
  • ConditionRouter.java
/**
     *分組正則匹配
     */
    private static Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
    /**
     * 當路由結果爲空時,是否強制執行,如果不強制執行,路由結果爲空的路由規則將自動失效,可不填,缺省爲 false 。
     */
    private final boolean force;
    /**
     * 消費者匹配條件集合,通過解析【條件表達式 rule 的 `=>` 之前半部分】
     */
    private final Map<String, MatchPair> whenCondition;
    /**
     * 提供者地址列表的過濾條件,通過解析【條件表達式 rule 的 `=>` 之後半部分】
     */
    private final Map<String, MatchPair> thenCondition;

public ConditionRouter(URL url) {
        this.url = url;
        //獲取url中的priority屬性
        this.priority = url.getParameter(Constants.PRIORITY_KEY, DEFAULT_PRIORITY);
        //獲取force屬性
        this.force = url.getParameter(Constants.FORCE_KEY, false);
        try {
            //獲取rule屬性
            String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
            if (rule == null || rule.trim().length() == 0) {
                throw new IllegalArgumentException("Illegal route rule!");
            }
            //替換rule字符串中的consumer.屬性和provider.屬性
            rule = rule.replace("consumer.", "").replace("provider.", "");
            int i = rule.indexOf("=>");
            //分割消費者和提供者規則
            String whenRule = i < 0 ? null : rule.substring(0, i).trim();
            String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
            Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
            Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
            // 注意:應在業務級別上確定“何時條件”是否可以爲空。
            this.whenCondition = when;
            this.thenCondition = then;
        } catch (ParseException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

RegistryDirectory.refreshInvoker(List<URL> invokerUrls)

 /**
     * 將invokerURL列表轉換爲Invoker Map。 轉換規則如下:
     * 1.If URL has been converted to invoker, it is no longer re-referenced and obtained directly from the cache, and notice that any parameter changes in the URL will be re-referenced.
     * 2.If the incoming invoker list is not empty, it means that it is the latest invoker list
     * 3.If the list of incoming invokerUrl is empty, It means that the rule is only a override rule or a route rule, which needs to be re-contrasted to decide whether to re-reference.
     *
     * 1.如果已將URL轉換爲調用程序invoker,則將不再重新引用該URL並直接從緩存,並且請注意,URL中的任何參數更改都將被重新引用。
     * 2.如果傳入的調用者列表不爲空,則表示它是最新的調用者列表
     * 3.如果傳入的invokerUrl列表爲空,則表示該規則只是覆蓋規則或路由規則,需要重新進行比較以決定是否重新引用。
     * @param invokerUrls this parameter can't be null
     */
    // TODO: 2017/8/31 FIXME The thread pool should be used to refresh the address, otherwise the task may be accumulated.
    private void refreshInvoker(List<URL> invokerUrls) {
        // invokerUrls 僅有一個元素,且 url 協議頭爲 empty,此時表示禁用所有服務
        if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
                && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            //設置 forbidden 爲 true 禁止訪問
            this.forbidden = true;
            this.methodInvokerMap = null;
            //銷燬所有的invoker
            destroyAllInvokers();
        } else {
            //設置允許訪問標識
            this.forbidden = false;
            //將urlInvokerMap的值賦值給oldUrlInvokerMap
            Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap;
            // 如果invokerUrls爲空,並且已緩存的invokerUrls不爲空,將緩存中的invoker url添加到invokerUrls中,這裏可以說明如果providers目錄未發送變化
            if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
                invokerUrls.addAll(this.cachedInvokerUrls);
            } else {
                //將invokeUrls加入到緩存中,方便比較
                this.cachedInvokerUrls = new HashSet<URL>();
                this.cachedInvokerUrls.addAll(invokerUrls);
            }
            if (invokerUrls.isEmpty()) {
                return;
            }
            //將invokerUrls轉換成爲invoker
            Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);
            //將 newUrlInvokerMap 轉成方法名到 Invoker 列表的映射
            Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap);
            // 如果newUrlInvokerMap爲空打印錯誤日誌並返回
            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); // Close the unused Invoker
            } catch (Exception e) {
                logger.warn("destroyUnusedInvokers error. ", e);
            }
        }
    }
  • 1.判斷invokerUrls的size是否爲1並且僅有的一個invokerUrl是empty協議,這種情況設置forbidden爲true,代表不可用,methodInvokerMap爲null,並且銷燬RegistryDirectory中的urlInvokerMap Map<String, Invoker<T>>
  • 2.如果invokerUrls爲空將cachedInvokerUrls加入invokerUrls否則緩存invokerUrls到cachedInvokerUrls中
  • 3.調用toInvokers方法,根據invokerUrls創建Invokers,並使用invokerUrl與invoker對應起來Map<String, Invoker<T>> newUrlInvokerMap
  • 4.調用toMethodInvokers方法,將Map<String, Invoker<T>> newUrlInvokerMap轉換成方法名稱與List<Invoker>的map Map<String, List<Invoker<T>>> newMethodInvokerMap
  • 5.根據是否爲多組調用toMergeMethodInvokerMap方法處理並賦值到Map<String, List<Invoker<T>>> methodInvokerMap屬性
  • 6.銷燬未被使用的Invokers

destroyAllInvokers()

private void destroyAllInvokers() {
        //本地已經保存了的Map<String, Invoker<T>> urlInvokerMap
        Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
        if (localUrlInvokerMap != null) {
            for (Invoker<T> invoker : new ArrayList<Invoker<T>>(localUrlInvokerMap.values())) {
                try {
                    //調用invoker的destroy方法
                    invoker.destroy();
                } catch (Throwable t) {
                    logger.warn("Failed to destroy service " + serviceKey + " to provider " + invoker.getUrl(), t);
                }
            }
            localUrlInvokerMap.clear();
        }
        methodInvokerMap = null;
    }

遍歷所有的invoker調用detroy方法

DubboInvoker.destroy()

@Override
    public void destroy() {
        // in order to avoid closing a client multiple times, a counter is used in case of connection per jvm, every
        // time when client.close() is called, counter counts down once, and when counter reaches zero, client will be
        // closed.
        //爲了避免多次關閉客戶端,在每個jvm進行連接的情況下,每次調用client.close()時,
        // 都會使用一個計數器,計數器遞減一次,而當計數器達到零時,客戶端將被關閉。
        if (super.isDestroyed()) {
            return;
        } else {
            // double check to avoid dup close
            destroyLock.lock();
            try {
                if (super.isDestroyed()) {
                    return;
                }
                super.destroy();
                if (invokers != null) {
                    invokers.remove(this);
                }
                for (ExchangeClient client : clients) {
                    try {
                        client.close(ConfigUtils.getServerShutdownTimeout());
                    } catch (Throwable t) {
                        logger.warn(t.getMessage(), t);
                    }
                }

            } finally {
                destroyLock.unlock();
            }
        }
    }

調用exchangeClient的close方法關閉客戶端,具體關閉流程不在本篇幅的範圍內

toInvokers(List<URL> urls)

/**
     * 將url轉換爲調用程序,如果已引用url,則不會重新引用。
     * @param urls
     * @return invokers
     */
    private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
        Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
        if (urls == null || urls.isEmpty()) {
            return newUrlInvokerMap;
        }
        Set<String> keys = new HashSet<String>();
        //獲取服務消費端配置的協議
        String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
        for (URL providerUrl : urls) {
            // If protocol is configured at the reference side, only the matching protocol is selected
            // 如果在消費端配置了協議,則僅選擇匹配的協議
            if (queryProtocols != null && queryProtocols.length() > 0) {
                boolean accept = false;
                String[] acceptProtocols = queryProtocols.split(",");
                for (String acceptProtocol : acceptProtocols) {
                    if (providerUrl.getProtocol().equals(acceptProtocol)) {
                        accept = true;
                        break;
                    }
                }
                // 若服務消費者協議頭不被消費者所支持,則忽略當前 providerUrl
                if (!accept) {
                    continue;
                }
            }
            //忽略empty協議
            if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
                continue;
            }
            //獲取消費端是否支持服務端的的協議,不支持打印錯誤日誌
            if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
                logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()
                        + ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions()));
                continue;
            }
            //合併Url,即提供者的配置合併
            URL url = mergeUrl(providerUrl);
            String key = url.toFullString();
            //忽略重複的Url
            if (keys.contains(key)) {
                continue;
            }
            keys.add(key);
            // 將本地url與invoker的緩存 賦值給localUrlInvokerMap
            Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
            //獲取緩存中該url對應的invoker
            Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
            //不存在與緩存中
            if (invoker == null) {
                try {
                    boolean enabled = true;
                    //如果url中配置了disable屬性
                    if (url.hasParameter(Constants.DISABLED_KEY)) {
                        enabled = !url.getParameter(Constants.DISABLED_KEY, false);
                    } else {
                        enabled = url.getParameter(Constants.ENABLED_KEY, true);
                    }
                    //如果沒有禁用 調用通過protocol.refer方法獲取Invoker
                    if (enabled) {
                        invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
                    }
                } catch (Throwable t) {
                    logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
                }
                if (invoker != null) { // Put new invoker in cache
                    newUrlInvokerMap.put(key, invoker);
                }
            } else {
                //加入緩存
                newUrlInvokerMap.put(key, invoker);
            }
        }
        keys.clear();
        return newUrlInvokerMap;
    }
  • 1.首先是通過protocol過濾掉消費者不需要或者不支持的providerUrl(dubbo://xxxx)
  • 2.調用mergeUrl方法進行providerUrl配置的合併Url的參數。 順序爲:override> -D>消費者>提供程序
  • 3.通過keys Set<String>集合進行providerUrl的去重,如果providerUrl已經創建invoker了,進入下一輪循環
  • 4.然後從緩存urlInvokerMap Map<String,Invoker<T>>去取,沒取到則創建!然後加入到newUrlInvokerMap Map<String,Invoker>中,並返回,其中創建是需要我們重點關注的點,我們單獨開個篇幅分析!。

mergeUrl(URL providerUrl)

/**
     * 合併網址參數。 順序爲:override> -D>消費者>提供程序
     * @param providerUrl
     * @return
     */
    private URL mergeUrl(URL providerUrl) {
        //合併服務消費的queryMap到providerUrl:
        //dubbo://169.254.22.149:20880/com.alibaba.dubbo.study.day01.xml.service.EchoService?addListener.1.callback=true&
        //addListener.retries=2&anyhost=true&application=echo-provider&bean.name=com.alibaba.dubbo.study.day01.xml.service.EchoService&
        //dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&
        //methods=echo,addListener&pid=27968&side=provider&timestamp=1573694769457
        providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // Merge the consumer side parameters
        //從configurators解析出來的override://xxx 配置用於覆蓋providerUrl的配置!
        List<Configurator> localConfigurators = this.configurators; // local reference
        if (localConfigurators != null && !localConfigurators.isEmpty()) {
            for (Configurator configurator : localConfigurators) {
                providerUrl = configurator.configure(providerUrl);
            }
        }
        //添加check屬性到providerUrl中
        providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false));

        // directoryUrl和override的組合位於notify的末尾,此處無法處理
        this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters()); // Merge the provider side parameters
        // 變換providerUrl的path
        if ((providerUrl.getPath() == null || providerUrl.getPath().length() == 0)
                && "dubbo".equals(providerUrl.getProtocol())) { // Compatible version 1.0
            //fix by tony.chenl DUBBO-44
            String path = directoryUrl.getParameter(Constants.INTERFACE_KEY);
            if (path != null) {
                int i = path.indexOf('/');
                if (i >= 0) {
                    path = path.substring(i + 1);
                }
                i = path.lastIndexOf(':');
                if (i >= 0) {
                    path = path.substring(0, i);
                }
                providerUrl = providerUrl.setPath(path);
            }
        }
        return providerUrl;
    }

toMethodInvokers(Map<String, Invoker<T>> invokersMap)

/**
     * Transform the invokers list into a mapping relationship with a method
     * 得到 <methodName, Invoker 列表> 映射關係
     * @param invokersMap Invoker Map
     * @return Mapping relation between Invoker and method
     */
    private Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) {
        //方法名 與 invoker的列表
        Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>();
        List<Invoker<T>> invokersList = new ArrayList<Invoker<T>>();
        //invokersMap的非空判斷
        if (invokersMap != null && invokersMap.size() > 0) {
            //遍歷invokers
            for (Invoker<T> invoker : invokersMap.values()) {
                //獲取url中的methods屬性
                String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY);
                //如果存在methods屬性
                if (parameter != null && parameter.length() > 0) {
                    //使用,分割methods屬性值
                    String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter);
                    if (methods != null && methods.length > 0) {
                        //遍歷methods
                        for (String method : methods) {
                            //method方法名稱不爲null並且不是*
                            if (method != null && method.length() > 0
                                    && !Constants.ANY_VALUE.equals(method)) {
                                //根據方法名獲取 Invoker 列表
                                List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
                                if (methodInvokers == null) {
                                    methodInvokers = new ArrayList<Invoker<T>>();
                                    newMethodInvokerMap.put(method, methodInvokers);
                                }
                                //存儲 Invoker 到列表中
                                methodInvokers.add(invoker);
                            }
                        }
                    }
                }
                invokersList.add(invoker);
            }
        }
        //服務級別路由
        List<Invoker<T>> newInvokersList = route(invokersList, null);
        //存儲 <*, newInvokersList> 映射關係,就是任何方法都支持的invoker列表
        newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList);
        //如果RegistryDirectory的serviceMethods屬性不爲空
        if (serviceMethods != null && serviceMethods.length > 0) {
            for (String method : serviceMethods) {
                List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
                if (methodInvokers == null || methodInvokers.isEmpty()) {
                    methodInvokers = newInvokersList;
                }
                newMethodInvokerMap.put(method, route(methodInvokers, method));
            }
        }
        // 排序,轉成不可變列表
        for (String method : new HashSet<String>(newMethodInvokerMap.keySet())) {
            List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
            Collections.sort(methodInvokers, InvokerComparator.getComparator());
            newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers));
        }
        return Collections.unmodifiableMap(newMethodInvokerMap);
    }

通過遍歷Map<String,Invoker<T>> newUrlInvokerMap,得到invokerList,還有保存了key-methodName value-List<Invoker>的map集合newMethodInvokerMap Map<String, List<Invoker<T>>>,首先對invokerList進行route路由的List<Invoker>然後加入到newMethodInvokerMap中,然後判斷RegistryDirectory.serviceMethods的屬性是否爲空,進行方法級別的路由騷操作後重新更換newMethodInvokerMap methodName對應的List<Invoker>,最後一步對每個methodName的List<Invoker>排序後修改爲不可變集合後返回;關於路由參考:https://blog.csdn.net/qq_23536449/article/details/103054529

route(List<Invoker<T>> invokers, String method)

private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) {
        Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
        //獲取routers路由
        List<Router> routers = getRouters();
        //路由不爲Null
        if (routers != null) {
            for (Router router : routers) {
                // If router's url not null and is not route by runtime,we filter invokers here
                //如果路由器的url不爲null並且不在運行時路由,則在此處過濾調用程序
                if (router.getUrl() != null && !router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
                    invokers = router.route(invokers, getConsumerUrl(), invocation);
                }
            }
        }
        return invokers;
    }

關於路由做了什麼事情,另起篇幅探討。

toMergeMethodInvokerMap(Map<String, List<Invoker<T>>> methodMap)

private Map<String, List<Invoker<T>>> toMergeMethodInvokerMap(Map<String, List<Invoker<T>>> methodMap) {
        Map<String, List<Invoker<T>>> result = new HashMap<String, List<Invoker<T>>>();
        //遍歷methodmap key爲方法名稱 value爲List<Invoker>
        for (Map.Entry<String, List<Invoker<T>>> entry : methodMap.entrySet()) {
            String method = entry.getKey();
            List<Invoker<T>> invokers = entry.getValue();
            //key爲group value爲List<Invoker>
            Map<String, List<Invoker<T>>> groupMap = new HashMap<String, List<Invoker<T>>>();
            for (Invoker<T> invoker : invokers) {
                String group = invoker.getUrl().getParameter(Constants.GROUP_KEY, "");
                List<Invoker<T>> groupInvokers = groupMap.get(group);
                if (groupInvokers == null) {
                    groupInvokers = new ArrayList<Invoker<T>>();
                    groupMap.put(group, groupInvokers);
                }
                groupInvokers.add(invoker);
            }
            // 如果 groupMap 中僅包含一組鍵值對,此時直接取出該鍵值對的值即可
            if (groupMap.size() == 1) {
                result.put(method, groupMap.values().iterator().next());
                // groupMap.size() > 1 成立,表示 groupMap 中包含多組鍵值對,比如:
                // {
                //     "group1": [invoker1, invoker2, invoker3, ...],
                //     "group2": [invoker4, invoker5, invoker6, ...]
                // }
            } else if (groupMap.size() > 1) {
                List<Invoker<T>> groupInvokers = new ArrayList<Invoker<T>>();
                // 通過集羣類合併每個分組對應的 Invoker 列表
                for (List<Invoker<T>> groupList : groupMap.values()) {
                    groupInvokers.add(cluster.join(new StaticDirectory<T>(groupList)));
                }
                result.put(method, groupInvokers);
            } else {
                result.put(method, invokers);
            }
        }
        return result;
    }

遍歷newMethodInvokerMap,對每個methodName的List<Invoker>處理,如果List<Invoker>只屬於一個組那麼不需要做什麼,否則的話,使用cluster.join進行合併組處理這樣子就得到了一個ClusterInvoker,然後更新methodName對應的List<Invoker>。Cluster是dubbo的一個SPI擴展接口Cluster$Adaptive

package com.alibaba.dubbo.rpc.cluster;

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

public class Cluster$Adaptive 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.RpcException {
        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);
    }
}

限於篇幅太長,Cluster放在後面進行分析好了。

服務註冊與動態發現機制原理和細節總結:

  • 1.服務提供者暴露服務時,向服務註冊中心註冊自己,即在註冊中心上創建/dubbo/service/providers/下創建個臨時節點,服務提供者與註冊中心保持長連接,一旦連接斷開或者會話失效後,註冊中心認爲該服務提供者不可用。
  • 2.服務消費者在啓動時,向註冊中心註冊自己,即在註冊中心上添加一個臨時節點/dubbo/service/consumers/.
  • 3.服務消費者訂閱/dubbo/service/providers、configurators、consumers三個目錄節點,如果這三個臨時目錄節點下內容發生改變,都會通知到消費者,根據目錄節點下配置的元數據信息,重新引用Invoker實現。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章