深度解析dubbo集群之抽象实现

本文基于dubbo v2.6.x

1. 介绍

在《深度解析dubbo集群之API》一文中,我们介绍Cluster 接口以及它的实现类与实现类的功能,通过上文学习,我们知道它每个实现类的join方法里面都会创建一个与之功能对应的invoker, 本文我们就来介绍下invoker是怎样实现对应功能的。首先我们先来看看那些invoker的继承关系
在这里插入图片描述
我们可以看到,它们继承AbstractClusterInvoker 这个抽象类,这个抽象类实现Invoker接口,重写invoke()方法,继承这个抽象类的需要实现doInvoke方法。我们来具体看下AbstractClusterInvoker 抽象类。

2. AbstractClusterInvoker源码解析

首先看下AbstractClusterInvoker的成员变量,我们重点关注的是directory 成员,这个对象就可以理解为目录,里面有一堆的该接口的服务提供者的invoker,我们获取服务提供者列表就是找它拿的

    protected final Directory<T> directory;//服务提供者的一个目录
    protected final boolean availablecheck; //集群中是否排除不可用的invoker
    // 判断是否已经销毁
    private AtomicBoolean destroyed = new AtomicBoolean(false);
 
    private volatile Invoker<T> stickyInvoker = null;

再来看看它的构造方法:

 public AbstractClusterInvoker(Directory<T> directory) {
        this(directory, directory.getUrl());
    }

 public AbstractClusterInvoker(Directory<T> directory, URL url) {
      if (directory == null)//验证参数
          throw new IllegalArgumentException("service directory == null");

      this.directory = directory;

      //"cluster.availablecheck"  默认true  集群中是否排除不可用的invoker
      //sticky: invoker.isAvailable() should always be checked before using when availablecheck is true.
      this.availablecheck = url.getParameter(Constants.CLUSTER_AVAILABLE_CHECK_KEY, Constants.DEFAULT_CLUSTER_AVAILABLE_CHECK);
  }

可以看出构造中是对availablecheck ,directory赋值,接着看下其他方法:

 // 实现接口的invoker方法
    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();   // 检验是否销毁
        LoadBalance loadbalance = null;  //定义负载均衡

        // binding attachments into invocation.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {// 就是将一些公共的kv 设置到invocation中
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
        // 获取所有服务提供者的集合
        List<Invoker<T>> invokers = list(invocation);
        if (invokers != null && !invokers.isEmpty()) {

            // invokers 不是空  根据dubbo spi 获取负载均衡策略  默认是random
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
        }

        // 如果是异步调用,就设置调用编号
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);

        // 具体实现是由子类实现的
        return doInvoke(invocation, invokers, loadbalance);
    }

这个invoke方法重点看下,首先检查 是否销毁,然后将context上下文中的参数塞到invocation调用信息中,调用list方法获取服务提供者列表。如果服务提供者列表不是空的,然后根据你配置的负载均衡的值使用dubbo spi 获取其实例,默认是使用随机负载的。接着就是如果是一部调用的话,设置调用编号。最后交给子类来实现具体的调用,这里也就是调用doInvoke(该方法是个抽象方法,需要其子类实现)

 // 模版方法, 交由子类来实现
    protected abstract Result doInvoke(Invocation invocation, List<Invoker<T>> invokers,
                                       LoadBalance loadbalance) throws RpcException;

接下来我们看看list 方法是怎样获取服务提供者列表的。

 /**
     * 获取所有服务提供者的invoker
     * @param invocation
     * @return
     * @throws RpcException
     */
    protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
        // 从directory中获取所有的invokers
        List<Invoker<T>> invokers = directory.list(invocation);
        return invokers;
    }

可以看出来是调用的directory 的list方法获取的服务提供者列表,关于directory 我们后面会有文章讲解,现在只需要知道他能给我们服务提供者列表。我们再来看下这个抽象类的其他方法:

 /**
     * Select a invoker using loadbalance policy.</br>
     * a)Firstly, select an invoker using loadbalance. If this invoker is in previously selected list, or, 
     * if this invoker is unavailable, then continue step b (reselect), otherwise return the first selected invoker</br>
     * b)Reslection, the validation rule for reselection: selected > available. This rule guarantees that
     * the selected invoker has the minimum chance to be one in the previously selected list, and also 
     * guarantees this invoker is available.
     * 从候选的invoker 集合里面获取一个最终调用的invoker
     * @param loadbalance load balance policy
     * @param invocation
     * @param invokers invoker candidates
     * @param selected  exclude selected invokers or not
     * @return
     * @throws RpcException
     */
    protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
        if (invokers == null || invokers.isEmpty())
            return null;
        String methodName = invocation == null ? "" : invocation.getMethodName();
        // 是老使用一个invoker  默认是false的
        boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY);
        {
            //ignore overloaded method
            // 如果stickyInvoker 不是null 而且没有在invokers 中了, 则置为空
            if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
                stickyInvoker = null;
            }
            //ignore concurrency problem
            // 支持粘滞连接  && stickyInvoker 不是null  && (排除列表是null  或者排除列表中不包含stickyInvoker )
            if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
                if (availablecheck && stickyInvoker.isAvailable()) {//是否排除不可用的invoker
                    return stickyInvoker; // 直接返回 粘滞连接
                }
            }
        }
        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
        // 如果支持 粘滞连接  则设置 缓存 粘滞连接
        if (sticky) {
            stickyInvoker = invoker;
        }
        return invoker;
    }

在这个方法中出现了一个新概念:粘滞连接,官网对于它的解释:粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。可以看下官网文档:链接,可以看出来这个粘滞连接 开启,总会调用一个服务提供者,如果这个服务提供者不可用了,就换一台。关于粘滞连接配置可以看下官方文档。
好,我们看下代码,首先获取是否开启粘滞连接 ,如果开启了粘滞连接 ,粘滞连接不是null,然后没在排除列表中,直接返回这个粘滞连接invoker。如果没有开启粘滞连接 或者 粘滞连接 不可用, 接着调用doSelect方法选择出来一个invoker,最后开启粘滞连接就把这个invoker赋值给stickyInvoker ,返回选择出来的invoker。
我们再来看下doSelect放是怎样选择的。

//进行选择
    private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
        if (invokers == null || invokers.isEmpty())//  invoker 列表是空就返回null
            return null;
        if (invokers.size() == 1) // 就一个的话就 就用第一个
            return invokers.get(0);
        if (loadbalance == null) {  // 获取负载均衡策略   默认是random(随机)
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
        }

        // 使用策略进行选择
        Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

        //If the `invoker` is in the  `selected` or invoker is unavailable && availablecheck is true, reselect.
        if ((selected != null && selected.contains(invoker))    // selected 存在选择的invoker   或者 检测可用参数是true +  invoker 不可用
                || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
            try {

                // 进行重新选择
                Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
                if (rinvoker != null) { // 重新选择出来的不是null
                    invoker = rinvoker;
                } else { // 如果重新选择出来的是null
                    //Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
                    int index = invokers.indexOf(invoker);
                    try {
                        //Avoid collision  选择 后一个 或者是第一个invoker
                        invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0);
                    } catch (Exception e) {
                        logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
                    }
                }
            } catch (Throwable t) {
                logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
            }
        }
        return invoker;
    }

首先,如果服务提供者列表就一个元素的话,直接返回那一个,如果负载均衡对象loadbalance 是空的话,使用dubbo spi获取一个负载均衡实例,先使用负载均衡算法从服务提供者列表中选出来一个invoker,如果选择出来的这个invoker 不可用或者在selected列表中中了,就调用reselect方法进行重新选择,如果重新选出来的这个invoker不是null,就直接返回,如果是null,就根据之前选出来的那个invoker的位置选择服务提供者列表的第一个或者是它后面一个元素,就是 之前那个invoker 位置不是最后一个元素的话,就选它后面那个invoker,如果是最后一个就选第一个invoker。
接下来在来看看reselect 这个重选方法:

 /**
     * Reselect, use invokers not in `selected` first,
     * if all invokers are in `selected`,
     * just pick an available one using loadbalance policy.
     * 重新选择, 首先使用invokers 中没有在selected 里面的,
     * 如果所有的invokers 中都在selected 中,就根据负载均衡策略选择一个
     * @param loadbalance
     * @param invocation
     * @param invokers
     * @param selected
     * @return
     * @throws RpcException
     */
    private Invoker<T> reselect(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected, boolean availablecheck)
            throws RpcException {

        //Allocating one in advance, this list is certain to be used.
        List<Invoker<T>> reselectInvokers = new ArrayList<Invoker<T>>(invokers.size() > 1 ? (invokers.size() - 1) : invokers.size());

        //First, try picking a invoker not in `selected`.  只要没有选择过的。
        if (availablecheck) { // invoker.isAvailable() should be checked  检测是否可用
            for (Invoker<T> invoker : invokers) {    // 找出所有可用并且没有在selected 中的 invoker, 然后添加到reselectInvokers 中
                if (invoker.isAvailable()) {
                    if (selected == null || !selected.contains(invoker)) {
                        reselectInvokers.add(invoker);
                    }
                }
            }
            // 如果 reselectInvokers 不是空的, 就根据负载均衡的策略选择一个invoker
            if (!reselectInvokers.isEmpty()) {
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }
        } else { // do not check invoker.isAvailable()  不检测可用性
            for (Invoker<T> invoker : invokers) {   // 遍历invokers ,找出不在selected 中的invoker 添加到reselectInvokers中
                if (selected == null || !selected.contains(invoker)) {
                    reselectInvokers.add(invoker);
                }
            }// 不是空的话,就使用负载均衡策略 选择一个
            if (!reselectInvokers.isEmpty()) {
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }
        }
        // Just pick an available invoker using loadbalance policy    只要可用就可以了
        {
            if (selected != null) {  // 这个只选择可用的就行了, 在没在selected 列表中的就不管了。。。
                for (Invoker<T> invoker : selected) {
                    if ((invoker.isAvailable()) // available first
                            && !reselectInvokers.contains(invoker)) {
                        reselectInvokers.add(invoker);
                    }
                }
            }// 使用负载均衡策略 选择一个
            if (!reselectInvokers.isEmpty()) {
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }
        }
        return null;
    }

首先是判断是否检查服务提供者可用性,如果检查可用性的话,遍历服务提供者列表,如果对应服务提供者可用,然后又没有在排除集合中,就将它加入到reselectInvokers这个集合中,如果reselectInvokers这个集合不是空的,根据负载均衡算法选择一个invoker返回。
如果不检查可用性,只要是没有在排除列表中就加入到reselectInvokers这个集合中 ,如果集合不是空,就根据负载均衡算法选择一个返回。
如果这时候还没有找到一个合适的invoker,就遍历服务提供者列表,这个只选择可用的就行了, 并且没有被添加到reselectInvokers集合中,就添加到reselectInvokers 集合中,如果这个集合不是空,使用负载均衡算法选择一个返回。
好了,我们在看下这个抽象类的其他方法:


    @Override
    public Class<T> getInterface() {
        return directory.getInterface();
    }

    @Override
    public URL getUrl() {
        return directory.getUrl();
    }

    @Override
    public boolean isAvailable() {
        Invoker<T> invoker = stickyInvoker;
        if (invoker != null) {// 如果invoker不等于null 就使用invoker的

            return invoker.isAvailable();
        }
        // 否则就使用directory的
        return directory.isAvailable();
    }
    // 销毁
    @Override
    public void destroy() {
        if (destroyed.compareAndSet(false, true)) {
            directory.destroy(); // 先设置状态 ,然后调用directory的destroy操作
        }
    }

可以看出来getInterface ,getUrl都是获取的directory对象的,isAvailable 检查的是stickyInvoker的可用性或者是directory的可用性,然后这个销毁方法destroy ,销毁的时候先cas设置销毁状态,然后调用directory.destroy()方法进行销毁

3. 总结

本文主要是介绍了集群抽象类AbstractClusterInvoker的实现,主要解析了它的invoke方法,select 方法,doSelect方法,reselect方法等等,后面的文章我们将会一一介绍它的实现类。

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