深度解析dubbo集群之Directory

本文基于dubbo 2.6.x

1. Directory

Directory从字面意思是一个目录的意思,在dubbo中Directory 主要是服务调用者获取服务提供者列表的抽象,它有一个list方法,服务调用者可以通过这个list方法获取对应接口的服务提供者列表(invokers),我们看下它定义:
在这里插入图片描述
接着在看下它的实现,通过下面UML继承关系图,我们可以看到有一个AbstractDirectory 抽象实现,然后RegistryDirectory 与StaticDirectory 分别继承AbstractDirectory 抽象类,下面的内容主要是解析这三个类。
在这里插入图片描述

2.AbstractDirectory源码解析

AbstractDirectory 类是 Directory 接口的抽象实现,它主要是实现了list方法 , 实现了对路由Router的管理,同时也实现了路由规则的逻辑。
我们先来看下它的成员变量:

    //注册中心url
    private final URL url;
    // 是否已经销毁
    private volatile boolean destroyed = false;
    // 服务调用者url
    private volatile URL consumerUrl;
    //router 数组
    private volatile List<Router> routers;

这里需要我们引起注意的就是routers集合,该集合就是存放路由规则的,AbstractDirectory提供了对routers集合 setter与getter方法(这个我们后面看下),接下来看下它的构造:

 public AbstractDirectory(URL url) {
        this(url, null);
    }
public AbstractDirectory(URL url, List<Router> routers) {
   this(url, url, routers);
}
public AbstractDirectory(URL url, URL consumerUrl, List<Router> routers) {
   if (url == null)
       throw new IllegalArgumentException("url == null");
   this.url = url;
   this.consumerUrl = consumerUrl;

   // 设置routers
   setRouters(routers);
}

我们看下最后这个三参构造就可以了,第一个参数url是注册中心的url,第二个url是服务调用者的url,第三个参数则是路由规则集合,构造中setRouters(routers);设置路由,我们看下这个setRouters方法:

	public List<Router> getRouters() {
        return routers;
    }

	/**
     * 设置路由规则
     * @param routers
     */
    protected void setRouters(List<Router> routers) {
        // copy list
        routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers);
        // append url router   获取router参数值
        String routerkey = url.getParameter(Constants.ROUTER_KEY);
        if (routerkey != null && routerkey.length() > 0) {// 如果router参数值不是空的话, 获取对应的工厂
            RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
            routers.add(routerFactory.getRouter(url));
        }
        // append mock invoker selector  添加几个系统自带的路由处理
        routers.add(new MockInvokersSelector());// mock router
        routers.add(new TagRouter());           // tag router

        //路由排序
        Collections.sort(routers);
        this.routers = routers;
    }

上面我也把关于路由集合的getter方法放上了,大家知道有这个方法就可以了,回到setRouters方法体中,先是创建个集合赋值给router,并将参数router的元素塞到新集合中,接着是获取用户router 参数的属性值,通过router 属性用户(也就是我们程序员)可以设置使用什么样的路由,如果router 参数的属性值 不是空,使用dubbo spi 获取对应的路由工厂,工厂根据url获取对应的路由塞到routers集合中,接着 MockInvokersSelector与TagRouter 塞到routers集合中,最后是对路由集合排序,给成员变量routers赋值。
接下来再来看下实现Directory 接口的list方法,上面我们说过,通过该方法服务调用者能够获取到对应服务提供者的列表。

	 /**
     * 根据invocation信息获取invoker列表
     * @param invocation  调用信息
     * @return
     * @throws RpcException
     */
    @Override
    public List<Invoker<T>> list(Invocation invocation) throws RpcException {
        if (destroyed) { //判断是否销毁
            throw new RpcException("Directory already destroyed .url: " + getUrl());
        }
        // 调用子类实现
        List<Invoker<T>> invokers = doList(invocation);
        List<Router> localRouters = this.routers; // local reference
        // 根据路由规则筛选 router
        if (localRouters != null && !localRouters.isEmpty()) {
            for (Router router : localRouters) {// 根据路由将 invoker过滤一遍
                try {
                    // 获取runtime 属性值,缺省是false,当是true的时候就需要走路由了
                    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;
    }

先是判断是否销毁,调用doList 方法根据调用信息invocation 获取服务提供者列表,这个doList是个抽象方法,需要子类实现。

 // 子类实现
    protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;

接着就是是遍历routers集合,根据路由规则来过滤服务提供者列表,当router.getUrl==null 或者runtime属性值是true的时候才会每次调用走路由。
总结一下这个list方法,先是通过子类实现的doList方法获取服务提供者列表,然后根据配置的路由规则从服务提供者们获取一部分合适的服务提供者返回。

3.RegistryDirectory源码解析

我们先看下RegistryDirectory 的class定义:

public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener {...}

可以看到RegistryDirectory继承AbstractDirectory 抽象类,需要重写doList方法,然后又实现NotifyListener接口,实现该接口的notify方法。
解释下这个NotifyListener 接口,该接口就一个void notify(List<URL> urls);抽象,可以向注册中心订阅某个url,当发生变化的时候就会调用notify方法通知那些订阅者们,在服务调用者端服务引用调用RegistryProtocol类的doRefer方法的时候就是将某个url与this向注册中心订阅:
在这里插入图片描述
direatory调用自己的subscribe方法向注册中心订阅,可以看到subscribe 方法又调用了注册中心对象的subscribe,参数是url与this。
在这里插入图片描述
看完了RegistryDirectory class定义我们再来看下它的类静态成员与成员变量:

  private static final Logger logger = LoggerFactory.getLogger(RegistryDirectory.class);
    // cluster
    private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
    // routerFactory
    private static final RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension();
    // configurator
    private static final ConfiguratorFactory configuratorFactory = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getAdaptiveExtension();
    private final String serviceKey; // Initialization at construction time, assertion not null
    // 服务类型
    private final Class<T> serviceType; // Initialization at construction time, assertion not null
    // consumer url 配置项url
    private final Map<String, String> queryMap; // Initialization at construction time, assertion not null
    // 原始目录url
    private final URL directoryUrl; // Initialization at construction time, assertion not null, and always assign non null value
    // 服务方法数组
    private final String[] serviceMethods;
    // 是否引用多分组
    private final boolean multiGroup;
    // protocol
    private Protocol protocol; // Initialization at the time of injection, the assertion is not null
    //注册中心
    private Registry registry; // Initialization at the time of injection, the assertion is not null
    
    /**
     * 是否禁止访问
     *  1. 没有服务提供者
     *  2. 服务提供者被禁用
     */
    private volatile boolean forbidden = false;
    //覆写的目录 URL ,结合配置规则
    private volatile URL overrideDirectoryUrl; // Initialization at construction time, assertion not null, and always assign non null value

    private volatile URL registeredConsumerUrl;
       private volatile List<Configurator> configurators; // The initial value is null and the midway may be assigned to null, please use the local variable reference
    // URl  与 服务提供者invoker 的映射
    // Map<url, Invoker> cache service url to invoker mapping.
    private volatile Map<String, Invoker<T>> urlInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference
    // 方法名 与 服务提供者invoker的映射
    // Map<methodName, Invoker> cache service method to invokers mapping.
    private volatile Map<String, List<Invoker<T>>> methodInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference
    //服务提供者url缓存
    // Set<invokerUrls> cache invokeUrls to invokers mapping.
    private volatile Set<URL> cachedInvokerUrls; // The initial value is null and the midway may be assigned to null, please use the local variable reference

可以看到有很多,后面讲到哪个我们再细说。
接下来看下构造方法:

public RegistryDirectory(Class<T> serviceType, URL url) {
        super(url);
        if (serviceType == null)//判断接口类型
            throw new IllegalArgumentException("service type is null.");
        if (url.getServiceKey() == null || url.getServiceKey().length() == 0)
            throw new IllegalArgumentException("registry serviceKey is null.");
        this.serviceType = serviceType;
        this.serviceKey = url.getServiceKey();
        this.queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
        this.overrideDirectoryUrl = this.directoryUrl = url.setPath(url.getServiceInterface()).clearParameters().addParameters(queryMap).removeParameter(Constants.MONITOR_KEY);
        String group = directoryUrl.getParameter(Constants.GROUP_KEY, "");
        this.multiGroup = group != null && ("*".equals(group) || group.contains(","));

        //取得methods
        String methods = queryMap.get(Constants.METHODS_KEY);
        this.serviceMethods = methods == null ? null : Constants.COMMA_SPLIT_PATTERN.split(methods);
    }

可以看到就是一些成员变量的赋值操作。
接着看下实现NotifyListener接口的notify方法,当注册中心发生变化的时候,就会通知(调用)该方法。

 /**
     * 通知接口,订阅了注册中心,url有变动就会通知
     * @param urls The list of registered information , is always not empty. The meaning is the same as the return value of {@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}.
     */
    @Override
    public synchronized void notify(List<URL> urls) {
        // 对url进行分组
        List<URL> invokerUrls = new ArrayList<URL>();
        List<URL> routerUrls = new ArrayList<URL>();
        List<URL> configuratorUrls = new ArrayList<URL>();
        for (URL url : urls) {
            // 获取protocol
            String protocol = url.getProtocol();
            // 获取category分组信息             默认是providers
            String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            if (Constants.ROUTERS_CATEGORY.equals(category)// 如果是routers
                    || Constants.ROUTE_PROTOCOL.equals(protocol)) {
                routerUrls.add(url);
            } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)//configurators
                    || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
                configuratorUrls.add(url);
            } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {// providers
                // 就添加到invokerUrls里面
                invokerUrls.add(url);
            } else {
                logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
            }
        }
        // 处理配置规则url集合
        if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
            this.configurators = toConfigurators(configuratorUrls);
        }
        //处理路由规则url集合
        // 当url 有变化的时候,重新设置router
        if (routerUrls != null && !routerUrls.isEmpty()) {
            List<Router> routers = toRouters(routerUrls);//将routerUrl转换成url
            if (routers != null) { // null - do nothing
                setRouters(routers);
            }
        }
        List<Configurator> localConfigurators = this.configurators; // local reference
        // merge override parameters
        this.overrideDirectoryUrl = directoryUrl;
        if (localConfigurators != null && !localConfigurators.isEmpty()) {
            for (Configurator configurator : localConfigurators) {
                this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
            }
        }
        // providers  处理服务提供者url集合
        refreshInvoker(invokerUrls);
    }

在notify方法中,先是定一个了3个集合分别存放不同的url(就是将传过来的url进行分组),遍历urls,获取元素url的protocol与category参数值(缺省是providers),按照其protocol与category参数值 进行分组,分别将url放到不同的集合中。然后后面就是将不同类型的url转成对应的对象们,调用toConfigurators(configuratorUrls); 方法将configuratorUrls转成Configurator对象集合,调用toRouters(routerUrls);方法将routerUrls转成Router 对象集合,调用refreshInvoker(invokerUrls); 来处理服务提供者urls。
这里我们主要解析下refreshInvoker(invokerUrls)方法(toRouters() 与toConfigurators()方法解析分别放到 router讲解篇与configurator讲解篇中)看看是怎样处理服务提供者urls的。

 private void refreshInvoker(List<URL> invokerUrls) {

        // invokerUrls 就一个元素  而且Protocol是空的时候
        if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
                && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            // 设置不允许访问
            this.forbidden = true; // Forbid to access
            //将方法invoker map 置为空
            this.methodInvokerMap = null; // Set the method invoker map to null
            // 销毁所有invoker
            destroyAllInvokers(); // Close all invokers
        } else {// invokerUrls 不为空的时候

            // 允许访问
            this.forbidden = false; // Allow to access
            Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference

            //如果invokerUrl 是空的 说明是路由规则或配置规则发生改变,此时 invokerUrls 是空的,直接使用 cachedInvokerUrls 。
            if (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()) {//如果invokerUrls 是空的直接返回
                return;
            }

            // 将url转成invoker
            Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
            // 根据 invoker map 转成
            Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
            // state change
            // If the calculation is wrong, it is not processed.
            if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
                logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
                return;
            }
            this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
            this.urlInvokerMap = newUrlInvokerMap;
            try {
                destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
            } catch (Exception e) {
                logger.warn("destroyUnusedInvokers error. ", e);
            }
        }
    }

如果服务提供者url集合就一个元素并且这个url的protocol是empty 的时候,设置不允许访问,清空methodInvokerMap(这个map缓存着method与invoker们的一个关系) ,调用destroyAllInvokers 销毁所有缓存的invoker。
否则,设置forbidden =false允许访问,处理现在url与缓存的url,处理过了之后这个invokerUrls还是空的话,就直接return,接着就是调用toInvokers方法将url转成url与invoker的对应关系集合,调用toMethodInvokers 将invoker转成method与invokers的对应关系。最后是调用destroyUnusedInvokers 方法将不用的invoker销毁掉。 接着我们看下这个toInvokers 方法是怎样将urls转成url与invoker对应关系的。

 /**
     * Turn urls into invokers, and if url has been refer, will not re-reference.
     * 将url转成invoker
     * @param urls
     * @return invokers
     */
    private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
        Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
        if (urls == null || urls.isEmpty()) {// 是空的直接返会空map
            return newUrlInvokerMap;
        }
        Set<String> keys = new HashSet<String>();
        String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);//获取consumer 的  protocol
        for (URL providerUrl : urls) {
            // If protocol is configured at the reference side, only the matching protocol is selected
            if (queryProtocols != null && queryProtocols.length() > 0) {
                boolean accept = false;
                String[] acceptProtocols = queryProtocols.split(",");
                for (String acceptProtocol : acceptProtocols) {
                    if (providerUrl.getProtocol().equals(acceptProtocol)) {
                        accept = true;
                        break;
                    }
                }
                if (!accept) {
                    continue;
                }
            }
            if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {//如果是个empty protocol直接跳过去
                continue;
            }
            if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {// 服务提供者协议不存在的时候抛出异常
                logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()
                        + ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions()));
                continue;
            }
            URL url = mergeUrl(providerUrl);// 处理了一下providerUrl
            String key = url.toFullString(); // The parameter urls are sorted
            if (keys.contains(key)) { // Repeated url
                continue;
            }
            keys.add(key);
            // Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again
            Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
            Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);// 根据key从缓存里面获取invoker
            if (invoker == null) { // Not in the cache, refer again
                try {
                    boolean enabled = true;
                    if (url.hasParameter(Constants.DISABLED_KEY)) {
                        enabled = !url.getParameter(Constants.DISABLED_KEY, false);//disabled
                    } else {
                        enabled = url.getParameter(Constants.ENABLED_KEY, true);//enabled
                    }
                    if (enabled) {// 这里生成一个invoker
                        invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
                    }
                } catch (Throwable t) {
                    logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
                }
                if (invoker != null) { // Put new invoker in cache
                    newUrlInvokerMap.put(key, invoker);
                }
            } else {
                newUrlInvokerMap.put(key, invoker);
            }
        }
        keys.clear();
        return newUrlInvokerMap;
    }

先是判断urls是否为空,如果不为空就进行遍历,在循环中,判断服务调用者与服务提供者的protocol是否相同,服务提供者url的protocol是否为空,服务提供者url protocol协议在服务调用者端是否有对应的实现,接着调用 mergeUrl 方法处理了一下服务提供者url。这个mergeUrl 其实就是将一些参数设置成服务调用者端的,然后使用configrutor处理下,接着调用url.toFullString();方法生成一个key,这个key就是url与invoker对应map的key,使用这个key从urlInvokerMap 缓存中获取对应的invoker,如果没有的话,判断url中的disabled 或者enabled 值,这个就是设置是否可用的参数值,如果可用的话invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);生成invoker ,这个如果你看过《深度解析dubbo服务远程引用(initClient)》可以知道invoker是怎样生成的,其实这后面一堆东西,从dubboprotocol一直到了创建创建连接。后面就是将key 与invoker添加到map中,然后将map返回,这就ok了,重要的还是invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
接下来我们在看下toMethodInvokers 方法是怎样将method与invokers 关联在一起的。

private Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) {

        // 存放 method 方法名与 invoker 集合对应关系的
        Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>();
        // According to the methods classification declared by the provider URL, the methods is compatible with the registry to execute the filtered methods
        List<Invoker<T>> invokersList = new ArrayList<Invoker<T>>();
        if (invokersMap != null && invokersMap.size() > 0) {
            // 遍历
            for (Invoker<T> invoker : invokersMap.values()) {

                // methods方法名 字符串。
                String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY);//获取method方法名串
                if (parameter != null && parameter.length() > 0) {
                    //使用英文逗号切割methods 方法名串
                    String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter);//切割
                    if (methods != null && methods.length > 0) {
                        for (String method : methods) {
                            // 方法名 不是空, 并且不是*
                            if (method != null && method.length() > 0
                                    && !Constants.ANY_VALUE.equals(method)) {
                                // 没有设置过
                                List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
                                if (methodInvokers == null) {
                                    methodInvokers = new ArrayList<Invoker<T>>();
                                    newMethodInvokerMap.put(method, methodInvokers);
                                }
                                methodInvokers.add(invoker);
                            }
                        }
                    }
                }
                invokersList.add(invoker);
            }
        }
        // 通过router进行路由过滤
        List<Invoker<T>> newInvokersList = route(invokersList, null);

        // 将 * 为key  value 为路由过滤完的invoker列表  缓存到 map中
        newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList);

        // 这个serviceMethods 是创建RegistryDirectory对象的时候构造方法中处理的
        if (serviceMethods != null && serviceMethods.length > 0) {
            for (String method : serviceMethods) {
                // 根据method获取对应的invoker集合
                List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
                if (methodInvokers == null || methodInvokers.isEmpty()) {
                    methodInvokers = newInvokersList;
                }
                // 重新设置了一下,然后将 之前的methodInvokers 这个invoker集合使用router过滤了一下
                newMethodInvokerMap.put(method, route(methodInvokers, method));
            }
        }
        
        
        // sort and unmodifiable
        for (String method : new HashSet<String>(newMethodInvokerMap.keySet())) {// 进行排序
            List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
            Collections.sort(methodInvokers, InvokerComparator.getComparator());
            newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers));
        }
        return Collections.unmodifiableMap(newMethodInvokerMap);
    }

在toMethodInvokers 方法中,首先是遍历invoker集合,然后与method对应放到map中,接着是调用route 方法对invoker集合进行路由过滤,筛选出一部分合适的invoker,以*为key,以路由过滤出来的invoker集合为value 放到 map中,接着是遍历serviceMethods 中所有方法,根据方法名从map中获取invoker集合,如果是空,就设置成上面路由过滤出来的invoker,不管是不是空都要使用路由将对应的invoker列表过滤一遍。最后遍历map中的value获取invoker集合,进行排序。
到这,我们method 与invokers对应关系的解析就完成了。
接下来我们要看下 doList方法了。

 @Override
    public List<Invoker<T>> doList(Invocation invocation) {


        // forbiden  出现的情况 没有服务提供者或者是服务提供者都不能用
        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).");
        }
        
        
        List<Invoker<T>> invokers = null;
        Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference
        if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
            // 从调用信息中获取调用方法名
            String methodName = RpcUtils.getMethodName(invocation);
            // 获取参数
            Object[] args = RpcUtils.getArguments(invocation);
            
            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
            }
            if (invokers == null) {
                invokers = localMethodInvokerMap.get(methodName);
            }
            if (invokers == null) {
                invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
            }
            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方法中先是判断forbidden ,当没有服务提供者或者是服务提供者都不能用的时候会抛出异常。
接着就是从调用信息invcation获取调用方法名与参数,先是使用"方法名.第一个字符串参数或者枚举参数" 为key 从methodInvokerMap map中获取对应的invoker集合,如果没有,接着使用"方法名" 为key获取对应的invoker集合(这个一般就能获取到了),如果还是没有,则使用 “*” 为key 获取对应的invoker集合,再不行就拿map的迭代器第一个value了。我们可以看到它的粒度可以小到方法名+第一个参数。
接着我们在看下其他的方法:

 @Override
    public boolean isAvailable() {
        if (isDestroyed()) {
            return false;
        }
        Map<String, Invoker<T>> localUrlInvokerMap = urlInvokerMap;
        if (localUrlInvokerMap != null && localUrlInvokerMap.size() > 0) {
            for (Invoker<T> invoker : new ArrayList<Invoker<T>>(localUrlInvokerMap.values())) {
                if (invoker.isAvailable()) {
                    return true;
                }
            }
        }
        return false;
    }

看下isAvailable方法,先是判断是否销毁,接着就是遍历所有的invoker,只要有一个是可用的,就返回true。

 /**
     * 使用route 过滤 invoker
     * @param invokers
     * @param method
     * @return
     */
    private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) {
        Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
        List<Router> routers = getRouters();///获取routers
        if (routers != null) {
            for (Router router : routers) {
                // If router's url not null and is not route by runtime,we filter invokers here  router url不是null  && runtime属性是false 的时候过滤
                if (router.getUrl() != null && !router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
                    invokers = router.route(invokers, getConsumerUrl(), invocation);
                }
            }
        }
        return invokers;
    }

看下这个route方法,先是调用getRouters 方法(父类里面)获取所有的router,然后遍历router对invokers进行路由过滤。

4. StaticDirectory 解析

这个我们就简单看下,其实它的invokers服务提供者列表是构造中设置进去的。我们在看类继承关系的时候发现,MockDirectory 继承它。

public class StaticDirectory<T> extends AbstractDirectory<T> {
    private final List<Invoker<T>> invokers;// 服务提供者列表
    public StaticDirectory(List<Invoker<T>> invokers) {
        this(null, invokers, null);
    }
    public StaticDirectory(List<Invoker<T>> invokers, List<Router> routers) {
        this(null, invokers, routers);
    }
    public StaticDirectory(URL url, List<Invoker<T>> invokers) {
        this(url, invokers, null);
    }
    public StaticDirectory(URL url, List<Invoker<T>> invokers, List<Router> routers) {
        super(url == null && invokers != null && !invokers.isEmpty() ? invokers.get(0).getUrl() : url, routers);
        if (invokers == null || invokers.isEmpty())
            throw new IllegalArgumentException("invokers == null");
        this.invokers = invokers;
    }
    @Override
    public Class<T> getInterface() {
        return invokers.get(0).getInterface();
    }
    @Override
    public boolean isAvailable() {
        if (isDestroyed()) {
            return false;
        }
        for (Invoker<T> invoker : invokers) {
            if (invoker.isAvailable()) {
                return true;
            }
        }
        return false;
    }
    @Override
    public void destroy() {
        if (isDestroyed()) {
            return;
        }
        super.destroy();
        for (Invoker<T> invoker : invokers) {
            invoker.destroy();
        }
        invokers.clear();
    }
    @Override
    protected List<Invoker<T>> doList(Invocation invocation) throws RpcException {
        return invokers;
    }
}

我这里就一股脑把源码丢进去了,继承AbstractDirectory抽象类, 重写doList方法, 可以看到invokers是从构造方法中设置进去的,它的doList方法也是直接返会invokers集合。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页