dubbo源码分析之-Please check registry access list (whitelist/blacklist)

  • 2019 年 10 月 4 日
  • 筆記

业务方在使用时经常会出现如下错误

看起来像是黑白名单,但是实际是服务方通过zk通知过来的地址变成了EMPTY://这样的形式,通常我们会提供如下的解决方式

一般都能解决问题

今天我们通过源代码来进行一下简单的分析,先来看一下代码的UML图

异常出错的地方是在RegistryDirectory,此类实现了NotifyListener接口,该接口中有一个很重要的方法就是notify,是处理zk的watch事件的,当consumer端监听的节点发生数据变化时,会通知consumer端进行响应的处理,下面我们大概浏览一下代码

//处理zk的watch事件  public synchronized void notify(List<URL> urls) {      List<URL> invokerUrls = new ArrayList<URL>();      List<URL> routerUrls = new ArrayList<URL>();      List<URL> configuratorUrls = new ArrayList<URL>();      for (URL url : urls) {          String protocol = url.getProtocol();          String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);          if (Constants.ROUTERS_CATEGORY.equals(category)                  || Constants.ROUTE_PROTOCOL.equals(protocol)) {              routerUrls.add(url);          } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)                  || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {              configuratorUrls.add(url);          } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {              invokerUrls.add(url);          } else {              logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());          }      }      // configurators      if (configuratorUrls != null && configuratorUrls.size() >0 ){          this.configurators = toConfigurators(configuratorUrls);      }      // routers      if (routerUrls != null && routerUrls.size() >0 ){          List<Router> routers = toRouters(routerUrls);          if(routers != null){ // null - do nothing              setRouters(routers);          }      }      List<Configurator> localConfigurators = this.configurators; // local reference      // 合并override参数      this.overrideDirectoryUrl = directoryUrl;      if (localConfigurators != null && localConfigurators.size() > 0) {          for (Configurator configurator : localConfigurators) {              this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);          }      }      // providers      refreshInvoker(invokerUrls);  }
/**   * 根据invokerURL列表转换为invoker列表。转换规则如下:   * 1.如果url已经被转换为invoker,则不在重新引用,直接从缓存中获取,注意如果url中任何一个参数变更也会重新引用   * 2.如果传入的invoker列表不为空,则表示最新的invoker列表   * 3.如果传入的invokerUrl列表是空,则表示只是下发的override规则或route规则,需要重新交叉对比,决定是否需要重新引用。   * @param invokerUrls 传入的参数不能为null   */  private void refreshInvoker(List<URL> invokerUrls){      if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null              && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {          this.forbidden = true; // 禁止访问          this.methodInvokerMap = null; // 置空列表          destroyAllInvokers(); // 关闭所有Invoker      } else {          this.forbidden = false; // 允许访问          Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference          if (invokerUrls.size() == 0 && this.cachedInvokerUrls != null){              invokerUrls.addAll(this.cachedInvokerUrls);          } else {              this.cachedInvokerUrls = new HashSet<URL>();              this.cachedInvokerUrls.addAll(invokerUrls);//缓存invokerUrls列表,便于交叉对比          }          if (invokerUrls.size() ==0 ){             return;          }          Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls) ;// 将URL列表转成Invoker列表          Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 换方法名映射Invoker列表          // state change          //如果计算错误,则不进行处理.          if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0 ){              logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :"+invokerUrls.size() + ", invoker.size :0. urls :"+invokerUrls.toString()));              return ;          }          this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;          this.urlInvokerMap = newUrlInvokerMap;          try{              destroyUnusedInvokers(oldUrlInvokerMap,newUrlInvokerMap); // 关闭未使用的Invoker          }catch (Exception e) {              logger.warn("destroyUnusedInvokers error. ", e);          }      }  }

下面来看一下EMPTY URL是哪里来的

代码ZookeeperRegistry.java

private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {      List<URL> urls = toUrlsWithoutEmpty(consumer, providers);      if (urls == null || urls.isEmpty()) {         int i = path.lastIndexOf('/');         String category = i < 0 ? path : path.substring(i + 1);         //这里会添加empty url         URL empty = consumer.setProtocol(Constants.EMPTY_PROTOCOL).addParameter(Constants.CATEGORY_KEY, category);          urls.add(empty);      }      return urls;  }
private List<URL> toUrlsWithoutEmpty(URL consumer, List<String> providers) {     List<URL> urls = new ArrayList<URL>();      if (providers != null && providers.size() > 0) {          for (String provider : providers) {              provider = URL.decode(provider);              if (provider.contains("://")) {                  URL url = URL.valueOf(provider);                  if (UrlUtils.isMatch(consumer, url)) {                      urls.add(url);                  }              }          }      }      return urls;  }
//关键是这里会进行URL的过滤  public static boolean isMatch(URL consumerUrl, URL providerUrl) {      //比较接口      String consumerInterface = consumerUrl.getServiceInterface();      String providerInterface = providerUrl.getServiceInterface();      if( ! (Constants.ANY_VALUE.equals(consumerInterface) || StringUtils.isEquals(consumerInterface, providerInterface)) ) return false;        //比较目录(这里是在哪里用到的,占时还没有搞懂)      if (! isMatchCategory(providerUrl.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY),              consumerUrl.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY))) {          return false;      }      if (! providerUrl.getParameter(Constants.ENABLED_KEY, true)              && ! Constants.ANY_VALUE.equals(consumerUrl.getParameter(Constants.ENABLED_KEY))) {          return false;      }        String consumerGroup = consumerUrl.getParameter(Constants.GROUP_KEY);      String consumerVersion = consumerUrl.getParameter(Constants.VERSION_KEY);      String consumerClassifier = consumerUrl.getParameter(Constants.CLASSIFIER_KEY, Constants.ANY_VALUE);        String providerGroup = providerUrl.getParameter(Constants.GROUP_KEY);      String providerVersion = providerUrl.getParameter(Constants.VERSION_KEY);      String providerClassifier = providerUrl.getParameter(Constants.CLASSIFIER_KEY, Constants.ANY_VALUE);  //group,version,classifier  return (Constants.ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) || StringUtils.isContains(consumerGroup, providerGroup))             && (Constants.ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion))             && (consumerClassifier == null || Constants.ANY_VALUE.equals(consumerClassifier) || StringUtils.isEquals(consumerClassifier, providerClassifier));  }

这里过滤完后,会添加一个empty url,然后就会将forbidden关键字至为true,后面客户方调用时,就会直接抛出异常,提示不可用