概述
由前面【Dubbo】集群容错机制 文章可知,整个容错过程中,会先调用Directory#list方法来获取所有可用的Invoker列表。

首先,来看一下Directory的类图,Directory是顶层的接口。AbstractDirectory封装了通用的实现逻辑。抽象类包含RegistryDirectory和StaticDirectory两个子类。
AbstractDirectory:封装了通用逻辑,RegistryDirectory:Directory的动态列表实现,会自动从注册中心更新Invoker列表、配置信息、路由列表。StaticDirectory:Directory的静态列表实现,即将传入的Invoker列表封装成静态的Directory对象,里面的列表不会改变。
源码解析
AbstractDirectory#list方法
AbstractDirectory#list方法主要包含以下逻辑:
- 调用子类的
doList方法获取所有Invoker列表 - 遍历所有路由列表,过滤
Invoker,返回过滤之后的Invoker
代码如下所示:
public List<Invoker<T>> list(Invocation invocation) throws RpcException {if (destroyed) {throw new RpcException("Directory already destroyed .url: " + getUrl());}//调用子类实现的doList方法获取可用的Invoker列表List<Invoker<T>> invokers = doList(invocation);//获取路由列表List<Router> localRouters = this.routers; // local reference//如果路由列表不为空,则遍历路由列表,进行路由if (localRouters != null && !localRouters.isEmpty()) {for (Router router : localRouters) {try {if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {invokers = router.route(invokers, getConsumerUrl(), invocation);}} catch (Throwable t) {logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);}}}return invokers;}//模板方法protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;
StaticDirectory
StaticDirectory#doList方法逻辑比较简单,因为其Invoker列表是不变的,所以该方法会直接返回传入的Invoker列表。代码如下所示:
protected List<Invoker<T>> doList(Invocation invocation) throws RpcException {return invokers;}
RegistryDirectory
RegistryDirectory是一种动态服务目录,实现了NotifyListener接口。当注册中心服务配置发生变化后,RegistryDirectory可收到与当前服务相关的变化。收到变更通知后,RegistryDirectory可根据配置变更信息刷新Invoker列表。
RegistryDirectory中有两条比较重要的逻辑线,第一条,子类实现父类的doList方法;第二条,框架与注册中心的订阅,并动态更新本地Invoker列表、路由列表、配置信息的逻辑。
doList方法
首先我们来看一下doList方法,该方法主要用来从本地缓存里获取Invokers列表,代码如下所示:
public List<Invoker<T>> doList(Invocation invocation) {//当没有invoker时,该值会被置为trueif (forbidden) {//省略noProvider异常抛出}List<Invoker<T>> invokers = null;//获取本地缓存,方法对应的Invoker列表Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local referenceif (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {//方法名String methodName = RpcUtils.getMethodName(invocation);//参数Object[] args = RpcUtils.getArguments(invocation);//第一个参数是否为String或者enum类型,通过方法名加第一个参数查询Invokers列表if (args != null && args.length > 0 && args[0] != null&& (args[0] instanceof String || args[0].getClass().isEnum())) {invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter}//通过方法名查询Invokers列表if (invokers == null) {invokers = localMethodInvokerMap.get(methodName);}//通过*查询Invokers列表if (invokers == null) {invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);}//遍历map,取第一个if (invokers == null) {Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();if (iterator.hasNext()) {invokers = iterator.next();}}}return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;}
可以看到,doList方法主要是从本地缓存methodInvokerMap里获取Invoker列表,那么本地缓存是何时更新的呢?
notify方法
RegistryDirectory实现了NotifyListener接口,通过notify这个方法获取注册中心变更通知。该方法主要包含以下逻辑:
- 根据
url的category和protocol对url进行分类 - 将对应的列表转成
Router和Configurator列表 - 刷新
Invoker列表
代码如下所示:
public synchronized void notify(List<URL> urls) {//provider url, 路由url, 配置器urlList<URL> invokerUrls = new ArrayList<URL>();List<URL> routerUrls = new ArrayList<URL>();List<URL> configuratorUrls = new ArrayList<URL>();//遍历urls列表,通过protocol和category将url放入不同的列表for (URL url : urls) {String protocol = url.getProtocol();String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);if (Constants.ROUTERS_CATEGORY.equals(category)|| Constants.ROUTE_PROTOCOL.equals(protocol)) {routerUrls.add(url);} else if (Constants.CONFIGURATORS_CATEGORY.equals(category)|| Constants.OVERRIDE_PROTOCOL.equals(protocol)) {configuratorUrls.add(url);} else if (Constants.PROVIDERS_CATEGORY.equals(category)) {invokerUrls.add(url);} //省略日志输出代码}//将configuratorUrls里的url转成configuratorsif (configuratorUrls != null && !configuratorUrls.isEmpty()) {this.configurators = toConfigurators(configuratorUrls);}//将routerUrls里的url转成routersif (routerUrls != null && !routerUrls.isEmpty()) {List<Router> routers = toRouters(routerUrls);if (routers != null) { // null - do nothingsetRouters(routers);}}List<Configurator> localConfigurators = this.configurators; // local reference// merge override parametersthis.overrideDirectoryUrl = directoryUrl;if (localConfigurators != null && !localConfigurators.isEmpty()) {for (Configurator configurator : localConfigurators) {this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);}}//刷新Invoker列表refreshInvoker(invokerUrls);}
refreshInvoker方法
接下来看一下refreshInvoker方法,该方法主要包含以下逻辑:
- 判断是否禁用所有服务
- 将
url转成Invoker,再转成方法名对应的Invoker列表 - 缓存第2步结果到
methodInvokerMap中 - 销毁无用的
Invoker
代码如下所示:
private void refreshInvoker(List<URL> invokerUrls) {//如果invokerUrls列表只有一条记录且协议头=empty,则表示禁用所有服务if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {//这个标志在doList方法的开头就用到了,如果为true,doList直接抛NoProvider异常this.forbidden = true; // Forbid to access//将缓存设置为空,并销毁所有Invokersthis.methodInvokerMap = null; // Set the method invoker map to nulldestroyAllInvokers(); // Close all invokers}//正常的情况else {this.forbidden = false; // Allow to accessMap<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference//缓存invokerUrlsif (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {invokerUrls.addAll(this.cachedInvokerUrls);} else {this.cachedInvokerUrls = new HashSet<URL>();this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison}if (invokerUrls.isEmpty()) {return;}//将url转成InvokerMap<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map//转成方法名->Invoker列表Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map//转换出错了if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {//省略日志代码return;}//合并多个组的Invokerthis.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;this.urlInvokerMap = newUrlInvokerMap;try {//销毁无用的InvokerdestroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker} //省略异常抛出代码}}
关于refreshInvoker方法中所调用的方法此处不做分析。
