服务目录的用途在于查找Invoker以及服务提供者变更的时候进行刷新Invoker。 这点我们可以从它的父类接口的方法看出来。
public interface Directory<T> extends Node {
//获取一个服务的Invoker集合
List<Invoker<T>> list(Invocation invocation) throws RpcException;
}
这里同样的还有一个通用套路,就是一个抽象类AbstractDirectory来抽象出公共方法,然后提供抽象方法给子类实现。
我们从服务变更时刷新开始分析服务目录的作用。这里服务变更时调用的是notify方法,该方法来自NotifyListener的notify方法,实现该接口可以在服务提供者信息变更时通知到这个方法。
public synchronized void notify(List<URL> urls) {
//收集出每个服务对应的URL
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull)
.filter(this::isValidCategory)
.filter(this::isNotCompatibleFor26x)
.collect(Collectors.groupingBy(url -> {
if (UrlUtils.isConfigurator(url)) {
return CONFIGURATORS_CATEGORY;
} else if (UrlUtils.isRoute(url)) {
return ROUTERS_CATEGORY;
} else if (UrlUtils.isProvider(url)) {
return PROVIDERS_CATEGORY;
}
return "";
}));
List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
//刷新服务提供者
refreshOverrideAndInvoker(providerURLs);
}
上面的方法调用会来到下面这个重点方法
private void refreshInvoker(List<URL> invokerUrls) {
Assert.notNull(invokerUrls, "invokerUrls should not be null");
//这里正常情况下默认走的else逻辑,因为服务的协议默认为dubbo
if (invokerUrls.size() == 1
&& invokerUrls.get(0) != null
&& EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
this.forbidden = true; // Forbid to access
this.invokers = Collections.emptyList();
routerChain.setInvokers(this.invokers);
destroyAllInvokers(); // Close all invokers
} else {
//设置静止访问为否
this.forbidden = false; // Allow to access
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
if (invokerUrls == Collections.<URL>emptyList()) {
invokerUrls = new ArrayList<>();
}
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
//缓存url
this.cachedInvokerUrls = new HashSet<>();
this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
}
if (invokerUrls.isEmpty()) {
return;
}
//将url转换成invoker
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls
.toString()));
return;
}
List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
// pre-route and build cache, notice that route cache should build on original Invoker list.
// toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
//将最新的invokers保存到routerChain中
//routerChain是invoker的容器。
routerChain.setInvokers(newInvokers);
this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers;
this.urlInvokerMap = newUrlInvokerMap;
try {
//销毁没用的invoker:就是旧的invoker和新的invoker进行对比,旧的没用的直接移除掉
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
}
接下来就是目录的查找功能,list方法的实现在抽象类AbstractDirectory进行实现的,然后定义了一个doList让子类进行实现。
public List<Invoker<T>> doList(Invocation invocation) {
//如果被禁用,抛出异常
if (forbidden) {
// 1. No service provider 2. Service providers are disabled
throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " +
getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +
NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() +
", please check status of providers(disabled, not registered or in blacklist).");
}
//有多个组,那么合并后返回
if (multiGroup) {
return this.invokers == null ? Collections.emptyList() : this.invokers;
}
List<Invoker<T>> invokers = null;
try {
// Get invokers from cache, only runtime routers will be executed.
//从路由中获取出来invoker
invokers = routerChain.route(getConsumerUrl(), invocation);
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
return invokers == null ? Collections.emptyList() : invokers;
}
整体分析来看,就是在服务消费者服务启动的时候,会先注册服务到注册中心,并且订阅需要消费的服务,然后调用notify更新一次invoker。最后在代理调用的时候在用list来返回对应的服务列表。