ressURL cachedURL = oldURLs.remove(rawProvider);
if (cachedURL == null) {
cachedURL = createURL(rawProvider, copyOfConsumer, getExtraParameters());
if (cachedURL == null) {
continue;
}
}
newURLs.put(rawProvider, cachedURL);
}
}
// 清除老的缓存数据
evictURLCache(consumer);
// 更新stringUrls缓存
stringUrls.put(consumer, newURLs);
return new ArrayList<>(newURLs.values());
}
protected ServiceAddressURL createURL(String rawProvider, URL consumerURL,
Map<String, String> extraParameters) {
boolean encoded = true;
int paramStartIdx = rawProvider.indexOf(ENCODED_QUESTION_MARK);
if (paramStartIdx == -1) {
encoded = false;
}
// 解析rawProvider, 一分为二, 一个用来创建URLAddress、一个用来创建URLParam
String[] parts = URLStrParser.parseRawURLToArrays(rawProvider, paramStartIdx);
if (parts.length <= 1) {
return DubboServiceAddressURL.valueOf(rawProvider, consumerURL);
}
String rawAddress = parts[0];
String rawParams = parts[1];
boolean isEncoded = encoded;
// 从缓存stringAddress缓存中获取URLAddress, 不存在则创建
URLAddress address =
stringAddress.computeIfAbsent(rawAddress,
k -> URLAddress.parse(k, getDefaultURLProtocol(), isEncoded));
address.setTimestamp(System.currentTimeMillis());
// 从缓存stringParam缓存中获取URLParam, 不存在则创建
URLParam param = stringParam.computeIfAbsent(rawParams,
k -> URLParam.parse(k, isEncoded, extraParameters));
param.setTimestamp(System.currentTimeMillis());
// 用获取到的URLAddress、URLParam直接new创建ServiceAddressURL
ServiceAddressURL cachedURL = createServiceURL(address, param, consumerURL);
// 判断创建出来的ServiceAddressURL是否和当前消费者匹配, 不匹配返回null
if (isMatch(consumerURL, cachedURL)) {
return cachedURL;
}
return null;
}
2.4 ZooKeeperRegistry
2.4.1 注册
根据传入的URL生成该节点要在ZooKeeper服务端上注册的节点路径,值为如下形式:/dubbo/org.apache.dubbo.springboot.demo.DemoService/providers/服务信息,/dubbo是根路径,接下来是服务的接口名,然后/providers是类型信息(如果是消费者则是/consumers节点,路由信息则是/routers),最后是URL的字符串表示。得到节点路径后,会根据URL的dynamic参数(默认是true)决定在ZooKeeper服务端上创建的是临时节点,还是持久节点,默认是临时节点。
注册后数据结构如下图所示。
2.4.2 订阅
订阅的核心是通过ZooKeeperClient在指定的节点的添加ChildListener,当该节点的子节点数据发生变化时,ZooKeeper服务端会通知到该ChildListener的notify方法,然后调用到对应的NotifyListener方法,刷新消费者本地的服务提供者列表等信息。
doSubscribe方法主要分为两个分支:URL的interface参数明确指定了为*(订阅所有,通常实际使用中不会这么使用,Dubbo的控制后台会订阅所有)或者就订阅某个服务接口,接下来分析订阅某个指定服务接口这个分支代码。这块涉及到三个监听器(它们是一对一的):
1)NotifyListener,Dubbo中定义的通用的订阅相关的监听器。它是定义在dubbo-registry-api模块中的,不仅仅在ZooKeeper注册中心模块中使用。
2)ChildListener,Dubbo中定义的针对ZooKeeper注册中心的监听器,用来监听指定节点子节点的数据变化。
3)CuratorWatcher,Curator框架中的监听器,Curator是常用的ZooKeeper客户端,如果Dubbo采用其它ZooKeeper客户端工具,这块就是其它相关的监听器逻辑了。
当订阅的服务数据发生变化时,最先会触发到CuratorWatcher的process方法,process方法中会调用ChildListener的childChanged方法,在childChanged方法会继续触发调用到ZooKeeperRegistry的notify方法。这里有两点需要注意:
1)因为doSubscribe方法中通过ZooKeeperClient添加Watcher监听器时,使用的是usingWatcher,这是一次性的,所以在CuratorWatcher的实现CuratorWatcherImpl的process方法中,当收到ZooKeeper的变更数据推送时,会再次在path上注册Watcher。
2)在ChildListener的实现RegistryChildListenerImpl的doNotify方法中,会调用ZooKeeperRegistry的toUrlsWithEmpty将传入的字符串形式的服务提供者列表等数据转换成对应的ServiceAddressURL列表数据,以供后面使用。
明确了三个监听器的含义之后,接下来分析doSubscribe的逻辑就简单了。首先会调用toCategoriesPath方法获取三个path路径,分别是