服务的订阅和通知
Directory
Directory 继承自 Node 接口,Node 这个接口继承者比较多,像 Registry、Monitor、Invoker 等均继承了这个接口。这个接口包含了一个获取配置信息的方法 getUrl,实现该接口的类可以向外提供配置信息。另外,大家注意看 RegistryDirectory 实现了 NotifyListener 接口,当注册中心节点信息发生变化后,RegistryDirectory 可以通过此接口方法得到变更信息,并根据变更信息动态调整内部 Invoker 列表
在容错过程中(如FailoverClusterInvoker
)会使用Directory#list
来获取所有的invoke的列表。
模板模式
Directory
顶层接口AbstractDirectory
封装了通用实现逻辑:list等StaticDirectory
Directory的静态列表实现,提供静态的Invoke列表,将传入的Invoker列表封装成静态的Directory对象,里面的列表不会改变,在ReferenceConfig#createProxy
使用CLUSTER.join(new StaticDirectory(invokers))
RegistryDirectory
Directory的动态列表实现提供动态的Invoke列表。会自动从注册中心更新Invoker列表,配置信息、路由信息。
RegistryDirectory
RegistryDirectory实现两点:
(1) 框架与注册中心的订阅,并动态更新本地Invoker列表、路由列表、配置信息的逻辑
(2) 实现父类的doList方法
订阅与动态更新主要方法subscribe、notify、refreshInvoker,辅助方法toConfigurators、toRouter
服务的订阅subscribe
注册中心ZK 的节点订阅和通知
调用链
RegistryProtocol#refer
-> RegistryProtocol#doRefer
-> directory#subscribe
-> registry#subscribe
-->这个registry 是ZookeeperRegistry
ZookeeperRegistry
父类FailbackRegistry#subscribe
->ZookeeperRegistry.doSubscribe()
会去监听下面的节点的路径的子节点变动
/dubbo/org.apache.dubbo.demo.DemoService/providers
/dubbo/org.apache.dubbo.demo.DemoService/configurators
/dubbo/org.apache.dubbo.de mo.DemoService/routers
directory#subscribe
订阅某个URL的更新信息。Dubbo在引用每个需要RPC调用(refer)Bean的时候,会调用directory.subscribe
订阅这个bean的各种URL变化
public void subscribe(URL url) {
// 设置consumerUrl
setConsumerUrl(url);
// 把当前的RegistryDriectory作为listener,去监听zk节点的变化
CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
registry.subscribe(url, this);
}
FailbackRegistry#subscribe
移除失效的listener,调用doSubscribe进行订阅
// listener为RegistryDirectory
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
// 移除失效的listener
removeFailedSubscribed(url, listener);
try {
// 调用ZookeeperRegistry.doSubscribe 实现
doSubscribe(url, listener);
} catch (Exception e) {
Throwable t = e;
List<URL> urls = getCacheUrls(url);
if (CollectionUtils.isNotEmpty(urls)) {
notify(url, listener, urls);
logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
} else {
// If the startup detection is opened, the Exception is thrown directly.
boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
&& url.getParameter(Constants.CHECK_KEY, true);
boolean skipFailback = t instanceof SkipFailbackWrapperException;
if (check || skipFailback) {
if (skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
} else {
logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
}
// Record a failed registration request to a failed list, retry regularly
addFailedSubscribed(url, listener); // 失败重试
}
}
ZookeeperRegistry#doSubscribe
主要实现把所有Service层发起的订阅以及指定的Service层发起的订阅分开处理。
- 所有Service层类似于监控中心发起的订阅。
- 指定的Service层发起的订阅可以看作是服务消费者的订阅。
@Override
public void doSubscribe(final URL url, final NotifyListener listener) {
try {
if (ANY_VALUE.equals(url.getServiceInterface())) {
// 这里主要用与服务端和zk连接 注册服务
... // 省略部分代码
} else {
// 消费端和zk连接 订阅节点
List<URL> urls = new ArrayList<>();
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) {
// 如果之前该路径没有添加过listener,则创建一个map来放置listener
zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
// 如果没有添加过对于子节点的listener,则创建,通知服务变化 回调NotifyListener
listeners.putIfAbsent(listener, (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
zkListener = listeners.get(listener);
}
zkClient.create(path, false);
//添加path节点的当前节点及子节点监听,并且获取子节点信息
//也就是dubbo://ip:port/...
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
// 调用notify进行通知,对已经可用的列表进行通知
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
服务通知notify
FailbackRegistry.notify
调用FailbackRegistry.notify, 对参数进行判断。 然后调用AbstractRegistry.notify方法
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");
}
try {
doNotify(url, listener, urls);
} catch (Exception t) {
// Record a failed registration request to a failed list, retry regularly
addFailedNotified(url, listener, urls);
logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
}
protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
super.notify(url, listener, urls);
}
AbstractRegistry.notify
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 ((CollectionUtils.isEmpty(urls))
&& !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);
}
// keep every provider's category.
Map<String