Dubbo consumer-provider 服务匹配分析

开篇

 这篇文章用于分析Dubbo consumer匹配provider端URL的逻辑,一个简单的场景如在provider侧提供多版本的service的时候,consumer侧能够根据版本匹配到正确的接口并进行访问。

 通过这篇后如果发现provider侧服务发布,但是consumer端没有发现服务,就应该考虑到provider和consumer侧的服务的不匹配。


consumer服务匹配逻辑分析

  • 获取path目录下所有服务提供者children ,List<String> children = zkClient.addChildListener(path, zkListener)。
  • 针对所有的URL的集合children通过toUrlsWithEmpty(url, path, children)进行匹配逻辑判断。
public class ZookeeperRegistry extends FailbackRegistry {

    public void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            if (ANY_VALUE.equals(url.getServiceInterface())) {
                   // 忽略这部分代码
            } else {
                List<URL> urls = new ArrayList<>();
                for (String path : toCategoriesPath(url)) {
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                    if (listeners == null) {
                        zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
                        listeners = zkListeners.get(url);
                    }
                    ChildListener zkListener = listeners.get(listener);
                    if (zkListener == null) {
                        listeners.putIfAbsent(listener, 
                        (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(
                               url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
                        zkListener = listeners.get(listener);
                    }
                    zkClient.create(path, false);
                    // 获取providers、routes等目录的子节点
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    if (children != null) {
                        // 添加符合条件的URL连接,通过toUrlsWithEmpty()内部进行判断
                        urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }
                notify(url, listener, urls);
            }
        } catch (Throwable e) {
            
        }
    }
}


  • toUrlsWithEmpty()内部调用toUrlsWithoutEmpty()方法。
public class ZookeeperRegistry extends FailbackRegistry {

    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(PATH_SEPARATOR);
            String category = i < 0 ? path : path.substring(i + 1);
            URL empty = URLBuilder.from(consumer)
                    .setProtocol(EMPTY_PROTOCOL)
                    .addParameter(CATEGORY_KEY, category)
                    .build();
            urls.add(empty);
        }
        return urls;
    }
}


  • 针对每个provider执行isMatch()的逻辑匹配。
  • 逻辑匹配逻辑在UrlUtils.isMatch()当中匹配。
public class ZookeeperRegistry extends FailbackRegistry {

    private List<URL> toUrlsWithoutEmpty(URL consumer, List<String> providers) {
        List<URL> urls = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(providers)) {
            for (String provider : providers) {
                // 针对每个provider执行isMatch()的逻辑匹配
                provider = URL.decode(provider);
                if (provider.contains(PROTOCOL_SEPARATOR)) {
                    URL url = URL.valueOf(provider);
                    if (UrlUtils.isMatch(consumer, url)) {
                        urls.add(url);
                    }
                }
            }
        }
        return urls;
    }
}


  • 核心的匹配逻辑在isMatch()方法当中。
  • 前置判断Interface是否相同。
  • 前置判断category是否相同。
  • 前置判断enabled是否为true。
  • 判断provider和consumer的Group+Version+Classifier三者是否相同,不相同就返回false。
public class UrlUtils {

    public static boolean isMatch(URL consumerUrl, URL providerUrl) {
        String consumerInterface = consumerUrl.getServiceInterface();
        String providerInterface = providerUrl.getServiceInterface();
        // 比较Interface的服务名是否相同,同时考虑了"*"这种逻辑。
        if (!(ANY_VALUE.equals(consumerInterface)
                || ANY_VALUE.equals(providerInterface)
                || StringUtils.isEquals(consumerInterface, providerInterface))) {
            return false;
        }
        // 比较category是否相同
        if (!isMatchCategory(providerUrl.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY),
                consumerUrl.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY))) {
            return false;
        }
        // 比较enabled是否相同
        if (!providerUrl.getParameter(ENABLED_KEY, true)
                && !ANY_VALUE.equals(consumerUrl.getParameter(ENABLED_KEY))) {
            return false;
        }
        // 比较consumerGroup、consumerVersion 、consumerClassifier 三者关系
        String consumerGroup = consumerUrl.getParameter(GROUP_KEY);
        String consumerVersion = consumerUrl.getParameter(VERSION_KEY);
        String consumerClassifier = consumerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);

        String providerGroup = providerUrl.getParameter(GROUP_KEY);
        String providerVersion = providerUrl.getParameter(VERSION_KEY);
        String providerClassifier = providerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);
        // 只有Group+Version+Classifier三者相同的才相等。
        return (ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) 
                     || StringUtils.isContains(consumerGroup, providerGroup))
                && (ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion))
                && (consumerClassifier == null || ANY_VALUE.equals(consumerClassifier) 
                       || StringUtils.isEquals(consumerClassifier, providerClassifier));
    }
}


总结

  • consumer和provider在服务的核心匹配逻辑在于判断Group+Version+Classifier三者是否相同。

推荐阅读更多精彩内容