dubbo註冊中心訂閱最後都會調用notify,這個notify幹了什麼呢?後續研究

 

AbstractRegistry類的notify方法

/**
 * 通知監聽器,URL 變化結果。
 *
 * 數據流向 `urls` => {@link #notified} => {@link #properties} => {@link #file}
 *
 * @param url 消費者 URL
 * @param listener 監聽器
 * @param urls 通知的 URL 變化結果(全量數據)
 */
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);
    }
    //  `urls` 按照 `url.parameter.category` 分類,添加到集合
    // 注意,特殊情況,使用 curator 連接 Zookeeper 時,若是服務消費者,連接斷開,會出現 category=providers,configurations,routes
    Map<String, List<URL>> result = new HashMap<String, 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 對應的在 `notified` 中,通知的 URL 變化結果(全量數據)
    Map<String, List<URL>> categoryNotified = notified.get(url);
    if (categoryNotified == null) {
        notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
        categoryNotified = notified.get(url);
    }
    // 【按照分類循環】處理通知的 URL 變化結果(全量數據)
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        // 覆蓋到 `notified`
        // 當某個分類的數據爲空時,會依然有 urls 。其中 `urls[0].protocol = empty` ,通過這樣的方式,處理所有服務提供者爲空的情況。
        categoryNotified.put(category, categoryList);
        // 保存到文件
        saveProperties(url);
        // 通知監聽器
        listener.notify(categoryList);
    }
}
/**
 * 通知監聽器
 */
public interface NotifyListener {
    /**
     * 當收到服務變更通知時觸發。
     * <p>
     * 通知需處理契約:<br>
     * 1. 總是以服務接口和數據類型爲維度全量通知,即不會通知一個服務的同類型的部分數據,用戶不需要對比上一次通知結果。<br>
     * 2. 訂閱時的第一次通知,必須是一個服務的所有類型數據的全量通知。<br>
     * 3. 中途變更時,允許不同類型的數據分開通知,比如:providers, consumers, routers, overrides,允許只通知其中一種類型,但該類型的數據必須是全量的,不是增量的。<br>
     * 4. 如果一種類型的數據爲空,需通知一個empty協議並帶category參數的標識性URL數據。<br>
     * 5. 通知者(即註冊中心實現)需保證通知的順序,比如:單線程推送,隊列串行化,帶版本對比。<br>
     *
     * @param urls 已註冊信息列表,總不爲空,含義同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。
     */
    void notify(List<URL> urls);

}

package com.alibaba.dubbo.registry.integration;


/**
 * RegistryDirectory
 *
 * 基於註冊中心的 Directory 實現類
 */
public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener {
   

    /**
     * [url][服務提供者 Invoker 集合]的映射緩存
     */
    // Map<url, Invoker> cache service url to invoker mapping.
    private volatile Map<String, Invoker<T>> urlInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference
    /**
     * [方法名][服務提供者 Invoker 集合]的映射緩存
     */
    // Map<methodName, Invoker> cache service method to invokers mapping.
    private volatile Map<String, List<Invoker<T>>> methodInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference
 
    @Override
    public synchronized void notify(List<URL> urls) {
        // 根據 URL 的分類或協議,分組成三個集合 。
        List<URL> invokerUrls = new ArrayList<URL>(); // 服務提供者 URL 集合
        List<URL> routerUrls = new ArrayList<URL>();
        List<URL> configuratorUrls = new ArrayList<URL>();
        for (URL url : urls) {
            String protocol = url.getProtocol();
            String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) {
                routerUrls.add(url);
            } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
                configuratorUrls.add(url);
            } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
                invokerUrls.add(url);
            } else {
                logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
            }
        }
        // 處理配置規則 URL 集合
        // configurators
        if (!configuratorUrls.isEmpty()) {
            this.configurators = toConfigurators(configuratorUrls);
        }
        // 處理路由規則 URL 集合
        // routers
        if (!routerUrls.isEmpty()) {
            List<Router> routers = toRouters(routerUrls);
            if (routers != null) { // null - do nothing
                setRouters(routers);
            }
        }
        // 合併配置規則,到 `directoryUrl` 中,形成 `overrideDirectoryUrl` 變量。
        List<Configurator> localConfigurators = this.configurators; // local reference
        // merge override parameters
        this.overrideDirectoryUrl = directoryUrl;
        if (localConfigurators != null && !localConfigurators.isEmpty()) {
            for (Configurator configurator : localConfigurators) {
                this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
            }
        }
        // 處理服務提供者 URL 集合
        // providers 這裏會把信息更新到methodInvokerMap中,在this.doList()方法中,會用到此變量,會在AbstractDirectory.doList調用
        refreshInvoker(invokerUrls);
    }

    @Override
    public List<Invoker<T>> doList(Invocation invocation) {
        if (forbidden) {
            // 1. No service provider 2. Service providers are disabled
            throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
                "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +  NetUtils.getLocalHost()
                        + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist).");
        }
        List<Invoker<T>> invokers = null;
        Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference
        // 獲得 Invoker 集合
        if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
            // 獲得方法名、方法參數
            String methodName = RpcUtils.getMethodName(invocation);
            Object[] args = RpcUtils.getArguments(invocation);
            // 【第一】可根據第一個參數枚舉路由
            if (args != null && args.length > 0 && args[0] != null
                    && (args[0] instanceof String || args[0].getClass().isEnum())) {
//                invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter
                invokers = localMethodInvokerMap.get(methodName + args[0]); // The routing can be enumerated according to the first parameter
            }
            // 【第二】根據方法名獲得 Invoker 集合
            if (invokers == null) {
                invokers = localMethodInvokerMap.get(methodName);
            }
            // 【第三】使用全量 Invoker 集合。例如,`#$echo(name)` ,回聲方法
            if (invokers == null) {
                invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
            }
            // 【第四】使用 `methodInvokerMap` 第一個 Invoker 集合。防禦性編程。
            if (invokers == null) {
                Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
                if (iterator.hasNext()) {
                    invokers = iterator.next();
                }
            }
        }
        return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
    }
}

看到是那裏更新服務提供者的了把,然後消費者調用是會調用doList獲取最新的服務提供者列表的說以你懂得:

消費者B ==> 提供者A會經歷下面的鏈路

InvokerInvocationHandler.invoke==>MigrationInvoker.invoke==>MockClusterInvoker.invoke==>AbstractCluster.invoke
    ==>ClusterInterceptor.intercept==>AbstractClusterInvoker.invoke
        ==>AbstractClusterInvoker.list==>AbstractDirectory.list==>RegistryDirectory.doList

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