开篇
这篇文章用于分析Dubbo consumer匹配provider端URL的逻辑,一个简单的场景如在provider侧提供多版本的service的时候,consumer侧能够根据版本匹配到正确的接口并进行访问。
通过这篇后如果发现provider侧服务发布,但是consumer端没有发现服务,就应该考虑到provider和consumer侧的服务的不匹配。
consumer服务匹配逻辑分析
- 获取path目录下所有服务提供者children ,List 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三者是否相同。