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