dubbo源码系列9-集群容错之dubbo集群 Cluster

一、前沿

现在的应用为了解决单点故障的问题,通常都会将应用部署到至少两台机器上,对于负载较高的服务,还会部署更多的机器支撑业务。同理在 dubbo 中同样会有多个 provider 为同一服务提供服务,此时 consumer 就需要决定选择调用哪一个 provider。此外服务调用失败时的处理逻辑也需要考虑设计,比如:重试、抛出异常或者是只输出异常等,为了解决这些问题,dubbo 定义了集群接口 Cluster 以及 Cluster InvokerCluster 用途是将多个 provider 合并为一个 Cluster Invoker,并将这个 Invoker 暴露给 consumer,这样的话 consumer 只需要通过这个 Invoker 进行远程调用即可。至于具体该调用哪个 provider 以及调用失败后该如何处理等问题,全部交给集群模块处理。 集群模块是 provider 和 consumer 的中间层,为 consumer 屏蔽了 provider 的情况,consumer 只需要调用服务即可,而不需要关心 provider 的具体情况

本文涉及到的负载均衡选择 Invoker 这里没有详细讲解,有专门的文章分析了,如果想了解的话,请查看 负载均衡 文章


Dubbo 提供了多种集群实现,包含但不限于 FailoverCluster 、FailfastCluster、FailsafeCluster、FailbackCluster、ForkingCluster 等,如下图所示:

每种类型的集群实现用途不同,接下来会一一进行分析

二、集群容错

在分析dubbo集群源码前,我们有必要先了解一下集群容错的所有组件,其中包含:Cluster、Cluster Invoker、Directory、Router 和 LoadBalance 等,关系图如下:

dubbo 集群工作分为如下两个阶段:

1、第一个阶段是在 consumer 初始化期间,集群 Cluster 实现类为 consumer 创建 Cluster Invoker 实例,即上图中的 merge 操作

2、第二个阶段是在 consumer 进行远程调用时,以 FailoverClusterInvoker(默认) 为例,经历了如下过程:

1)、该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为 provider),调用  Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker

2)、FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker

3)、FailoverClusterInvoker 将参数传给 LoadBalance 选择出的 Invoker 实例的 invoker 方法,进行真正的远程调用

Directory: 用途是保存 Invoker,可简单类比为 List<Invoker>。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker

以上就是集群工作的整个流程,这里并没介绍集群是如何容错的。Dubbo 主要提供了以下常用的六种容错方式:

Failover Cluster:失败自动切换

Failfast Cluster:快速失败

Failsafe Cluster:失败安全

Failback Cluster:失败自动恢复

Forking Cluster:并行调用多个服务提供者

Broadcast Cluster:逐个调用服务提供者,一个异常就抛出错误

三、集群源码

3.1 Cluster 实现类

集群接口 Cluster  是一个接口,只有一个 join 方法,如下图:

Cluster 接口仅用于创建生成 Cluster Invoker。下面我们以 FailoverCluster(默认) 为例,来看一下源码:

public class FailoverCluster implements Cluster {

    public final static String NAME = "failover";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        // 创建并返回 FailoverClusterInvoker 对象
        return new FailoverClusterInvoker<T>(directory);
    }

}

所有类型的 Cluster 功能就一个,即创建生成 Cluster Invoker,逻辑相当简单,这里不做额外分析了。下面我们来看一下 Cluster Invoker 的源码

3.2 Cluster Invoker

Cluster Invoker 是一种 Invoker,provider 的选择逻辑,以及远程调用失败后的的处理逻辑均是封装在 Cluster Invoker 中。

前言中我们知道,集群工作过程可分为两个阶段,第一个阶段是在 consumer 初始化期间,这个在 服务引用的 Invoker 创建中 分析过,这里就不在赘述了。第二个阶段是在 consumer 进行远程调用时,此时 AbstractClusterInvoker(Cluster Invoker 的父类) 的 invoke 方法会被调用,列举 Invoker,负载均衡等操作均会在此阶段被执行。因此下面先来看一下 AbstractClusterInvoker 的 invoke 方法的逻辑,代码如下:

    // AbstractClusterInvoker 的 invoke 方法
    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        // 校验 Invoker 是否销毁了
        checkWhetherDestroyed();

        // binding attachments into invocation.
        // 将 RpcContext 中的 attachments 参数绑定到 RpcInvocation 中
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }

        // 从directory中获取 Invoker 列表
        List<Invoker<T>> invokers = list(invocation);
        // 初始化负载均衡策略,RandomLoadBalance 为默认的负载均衡策略
        // invokers 不为空时,则取第一个 invoker 的 url 中 loadbalance 参数设置的负载均衡策略
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        // 如果请求是异步的,需要设置 id 参数到 RpcInvocation 的 Attachment 中
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        // 调用具体的 Invoker 实现类,dubbo 中默认是 FailoverClusterInvoker,故这里调用 FailoverClusterInvoker 的 doInvoke 方法
        return doInvoke(invocation, invokers, loadbalance);
    }

AbstractClusterInvoker 的 invoke 方法主要做了以下工作:

1)、获取 Invoker 列表,以及初始化加载 LoadBalance

2)、调用模板方法 doInvoke ,即具体的 Invoker 实现类的实现方法

下面我们来看一下 Invoker 列举方法 list(Invocation) 的逻辑,debug 栈流程如下:

debug具体栈信息:

doList:581, RegistryDirectory (org.apache.dubbo.registry.integration)
list:85, AbstractDirectory (org.apache.dubbo.rpc.cluster.directory)
list:290, AbstractClusterInvoker (org.apache.dubbo.rpc.cluster.support)
invoke:249, AbstractClusterInvoker (org.apache.dubbo.rpc.cluster.support)

整个过程源码如下:

    // 1、AbstractClusterInvoker 的 list方法
    protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
        // 调用 AbstractDirectory 的 list 方法获取 Invoker 列表
        return directory.list(invocation);
    }


    // 2、AbstractDirectory 的 list 方法
    @Override
    public List<Invoker<T>> list(Invocation invocation) throws RpcException {
        if (destroyed) {
            throw new RpcException("Directory already destroyed .url: " + getUrl());
        }

        // 调用 RegistryDirectory 的 doList 方法
        return doList(invocation);
    }


    // 3、RegistryDirectory 的 doList 方法
    @Override
    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.
            // route 路由器职责链过滤满足条件的 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;
    }

获取 Invoker 列表之后,初始化加载了 LoadBalance,这个逻辑相对简单,这里就不在分析了,有兴趣的自己可以分析一下,下面重点讲解 AbstractClusterInvoker 的 不同实现类的 doInvoke 方法逻辑。

3.2.1 FailoverClusterInvoker

FailoverClusterInvoker 在调用失败时,会自动切换 Invoker 进行重试。默认配置下,Dubbo 会使用这个类作为缺省 Cluster Invoker。下面来看一下该类的 doInvoke 源码:

    public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        List<Invoker<T>> copyInvokers = invokers;
        checkInvokers(copyInvokers, invocation);
        // 获取方法名
        String methodName = RpcUtils.getMethodName(invocation);
        // 获取重试次数,最终值是获取的参数值 + 1,如果小于 0,则取 1
        int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        // 循环调用,失败重试
        RpcException le = null; // last exception.
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                checkWhetherDestroyed();
                // 在进行重试前重新列举 Invoker,这样做的好处是,如果某个服务挂了,
                // 通过调用 list 可得到最新可用的 Invoker 列表
                copyInvokers = list(invocation);
                // check again
                // 对获取的最新 Invoker 列表判断检查
                checkInvokers(copyInvokers, invocation);
            }
            // 通过负载均衡策略选择 Invoker
            Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
            // 添加到 invoker 到 invoked 列表中
            invoked.add(invoker);
            // 设置 invoked 列表到 RpcContext 上下文中
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                // 调用目标 Invoker 的 invoke 方法
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                    logger.warn("Although retry the method " + methodName
                            + " in the service " + getInterface().getName()
                            + " was successful by the provider " + invoker.getUrl().getAddress()
                            + ", but there have been failed providers " + providers
                            + " (" + providers.size() + "/" + copyInvokers.size()
                            + ") from the registry " + directory.getUrl().getAddress()
                            + " on the consumer " + NetUtils.getLocalHost()
                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
                            + le.getMessage(), le);
                }
                return result;
            } catch (RpcException e) {
                if (e.isBiz()) { // biz exception.
                    throw e;
                }
                le = e;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
                providers.add(invoker.getUrl().getAddress());
            }
        }
        throw new RpcException(le.getCode(), "Failed to invoke the method "
                + methodName + " in the service " + getInterface().getName()
                + ". Tried " + len + " times of the providers " + providers
                + " (" + providers.size() + "/" + copyInvokers.size()
                + ") from the registry " + directory.getUrl().getAddress()
                + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
                + Version.getVersion() + ". Last error is: "
                + le.getMessage(), le.getCause() != null ? le.getCause() : le);
    }

本方法中先获取重试次数,根据重试次数循环调用,如果调用失败的话循环重试,成功了直接返回结果。在循环体内,首先调用 父类 list 方法获取到最新的 Invoker 列表,然后调用 select 方法负载均衡选择一个 Invoker,最后再调用这个 Invoker 的 invoke 方法进行远程调用。如果调用失败,记录异常,循环重试。

下面我们来分析关键的 AbstractClusterInvoker 的 select 方法,该方法主要是通过负载均衡选择 Invoker ,源码如下:

    /**
     * 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>
     * <p>
     * b) Reselection, 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.
     *
     * @param loadbalance load balance policy
     * @param invocation  invocation
     * @param invokers    invoker candidates
     * @param selected    exclude selected invokers or not
     * @return the invoker which will final to do invoke.
     * @throws RpcException exception
     */
    // AbstractClusterInvoker 的 select方法
    protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {

        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        // 获取调用方法名
        String methodName = invocation == null ? StringUtils.EMPTY : invocation.getMethodName();

        // 获取 sticky 配置,默认值 false,sticky 表示粘滞连接。所谓粘滞连接是指让服务消费者尽可能的调用同一个服务提供者,除非该提供者挂了再进行切换
        boolean sticky = invokers.get(0).getUrl()
                .getMethodParameter(methodName, CLUSTER_STICKY_KEY, DEFAULT_CLUSTER_STICKY);

        //ignore overloaded method
        // 检测 invokers 列表是否包含 stickyInvoker,如果不包含,说明 stickyInvoker 代表的服务提供者挂了,此时需要将其置空
        if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
            stickyInvoker = null;
        }
        //ignore concurrency problem
        // 当 sticky 为 true,且 stickyInvoker != null 的情况下。如果 selected 包含 stickyInvoker,表明 stickyInvoker
        // 对应的服务提供者可能因网络原因未能成功提供服务。但是该提供者并没挂,此时 invokers 列表中仍存在该服务提供者对应的 Invoker,
        // 如果 selected 不包含 stickyInvoker,则表明 stickyInvoker 没有被选择过,则需要进一步检查 stickyInvoker 是否可用
        if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
            // availablecheck 表示是否开启了可用性检查,如果开启了,则调用 stickyInvoker 的 isAvailable 方法进行检查,如果检查通过,则直接返回 stickyInvoker
            if (availablecheck && stickyInvoker.isAvailable()) {
                return stickyInvoker;
            }
        }

        // 如果线程运行到当前代码处,说明前面的 stickyInvoker 为空,或者不可用。此时继续调用 doSelect 选择 Invoker
        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);

        // 如果 sticky 为 true,则将负载均衡组件选出的 Invoker 赋值给 stickyInvoker,保存起来,下次请求直接使用
        if (sticky) {
            stickyInvoker = invoker;
        }
        return invoker;
    }

select 方法主要实现了以下逻辑:

1)、获取 sticky 配置,默认 false

2)、检测 invokers(存活着的 Invoker 列表)是否包含 stickyInvoker,如果不包含,则说明 stickyInvoker 代表的服务提供者挂了,此时需要将其置空

3)、检测 stickyInvoker 是否可用,如果可用直接返回

4)、如果 stickyInvoker 为空 或者 不可用 或者 已经被选择过了,调用负载均衡重新选择 Invoker

5)、如果 sticky 为 true,此时会将 doSelect 方法选出的 Invoker 赋值给 stickyInvoker

我们继续看 AbstractClusterInvoker 的 doSelect 方法,源码如下:

    // AbstractClusterInvoker 的 doSelect方法
    private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {

        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        if (invokers.size() == 1) {
            return invokers.get(0);
        }
        // 负载均衡组件选择 Invoker
        Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

        //If the `invoker` is in the  `selected` or invoker is unavailable && availablecheck is true, reselect.
        // 如果负载均衡组件选择出的 invoker 已经包含在了 selected 中 或者 invoker 不可用 && availablecheck 为true,需要重新选择 Invoker
        if ((selected != null && selected.contains(invoker))
                || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
            try {
                // 调用 reselect 方法重新选择 Invoker
                Invoker<T> rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
                if (rInvoker != null) {
                    // 如果 rinvoker 不为空,则将其赋值给 invoker
                    invoker = rInvoker;
                } else {
                    //Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
                    // 获取 invoker 在 invokers 中的位置
                    int index = invokers.indexOf(invoker);
                    try {
                        //Avoid collision
                        // 获取 index + 1 对  invokers 长度 取模位置的 invoker,这样避免碰撞冲突
                        invoker = invokers.get((index + 1) % invokers.size());
                    } 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;
    }

doSelect 方法主要实现了以下逻辑:

1)、负载均衡组件选择 Invoker

2)、选出的 Invoker 如果被选择过了 或者 不可用,此时调用 reselect 方法进行重选

3)、若 reselect 选出来的 Invoker 为空,则取 invoker 在 invokers 中的位置 + 1 对 invokers 长度取模 位置的 Invoker

接着往下看 AbstractClusterInvoker 的 reselect 方法,源码如下:

    /**
     * Reselect, use invokers not in `selected` first, if all invokers are in `selected`,
     * just pick an available one using loadbalance policy.
     *
     * @param loadbalance    load balance policy
     * @param invocation     invocation
     * @param invokers       invoker candidates
     * @param selected       exclude selected invokers or not
     * @param availablecheck check invoker available if true
     * @return the reselect result to do invoke
     * @throws RpcException exception
     */
    // AbstractClusterInvoker 的 reselect方法
    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<>(
                invokers.size() > 1 ? (invokers.size() - 1) : invokers.size());

        // First, try picking a invoker not in `selected`.
        for (Invoker<T> invoker : invokers) {
            // 检测invoker的可用性
            if (availablecheck && !invoker.isAvailable()) {
                continue;
            }

            // 如果 selected 列表不包含当前 invoker,则将其添加到 reselectInvokers 中
            if (selected == null || !selected.contains(invoker)) {
                reselectInvokers.add(invoker);
            }
        }

        // reselectInvokers 不为空,此时通过负载均衡组件进行选择 Invoker
        if (!reselectInvokers.isEmpty()) {
            return loadbalance.select(reselectInvokers, getUrl(), invocation);
        }

        // Just pick an available invoker using loadbalance policy
        if (selected != null) {
            for (Invoker<T> invoker : selected) {
                // 如果 invoker 可用 && reselectInvokers 列表不包含当前 invoker,则将其添加到 reselectInvokers 中
                if ((invoker.isAvailable()) // available first
                        && !reselectInvokers.contains(invoker)) {
                    reselectInvokers.add(invoker);
                }
            }
        }

        // reselectInvokers 不为空,再次通过负载均衡组件进行选择 Invoker
        if (!reselectInvokers.isEmpty()) {
            return loadbalance.select(reselectInvokers, getUrl(), invocation);
        }

        return null;
    }

reselect 方法主要实现了以下逻辑:

1)、遍历活着的 invokers 列表,如果当前 invoker 可用 && selected 列表不包含当前 invoker,则将其添加到 reselectInvokers 中

2)、reselectInvokers 不为空时通过负载均衡组件选择 Invoker

3)、遍历选择过的 invokers(selected) 列表,如果当前 invoker 可用 && reselectInvokers 列表不包含当前 invoker,则将其添加到 reselectInvokers 中

4)、reselectInvokers 不为空时再次通过负载均衡组件选择 Invoker

到这里 FailoverClusterInvoker 就分析完了,下面继续分析其他的 Cluster Invoker

3.2.2 FailfastCluster

FailfastClusterInvoker 只会进行一次调用,调用失败后立即抛出异常

适用场景:适用于幂等操作,比如新增记录

源码如下:

public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {

    public FailfastClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        checkInvokers(invokers, invocation);
        // 调用 AbstractClusterInvoker 的 select方法选择 Invoker
        Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
        try {
            // 调用 Invoker 的 invoke 方法
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            // 调用失败,直接抛出异常
            if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception.
                throw (RpcException) e;
            }
            throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0,
                    "Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName()
                            + " select from all providers " + invokers + " for service " + getInterface().getName()
                            + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost()
                            + " use dubbo version " + Version.getVersion()
                            + ", but no luck to perform the invocation. Last error is: " + e.getMessage(),
                    e.getCause() != null ? e.getCause() : e);
        }
    }
}

FailfastClusterInvoker 的 doInvoke 方法逻辑很简单,先选择 Invoker,然后调用 Invoker 的 invoke 方法,调用失败后直接抛出异常,select 方法已经在 FailoverClusterInvoker 分析过了,这里不在赘述。

3.2.3 FailsafeClusterInvoker

FailsafeClusterInvoker 是一种失败安全的 Cluster Invoker。所谓的失败安全是指,当调用过程中出现异常,FailsafeClusterInvoker 仅会打印异常,而不会抛出异常

适用场景:适用于写入审计日志等操作

源码如下:

public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {
    private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class);

    public FailsafeClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            checkInvokers(invokers, invocation);
            // 调用 AbstractClusterInvoker 的 select方法选择 Invoker
            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
            // 调用 invoker 的 invoke 方法
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            // 调用失败,不抛出异常,只是打印输出异常
            logger.error("Failsafe ignore exception: " + e.getMessage(), e);
            // 返回空结果给 consumer
            return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
        }
    }
}

FailsafeClusterInvoker  的 doInvoke 逻辑简单,这里不在赘述

3.2.4  FailbackClusterInvoker

FailbackClusterInvoker 会在调用失败后,返回一个空结果给服务消费者。并通过定时任务对失败的调用进行重传

适用场景:执行消息通知等操作

源码如下:

public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {

    private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class);

    private static final long RETRY_FAILED_PERIOD = 5;

    private final int retries;

    private final int failbackTasks;

    private volatile Timer failTimer;

    public FailbackClusterInvoker(Directory<T> directory) {
        super(directory);

        int retriesConfig = getUrl().getParameter(RETRIES_KEY, DEFAULT_FAILBACK_TIMES);
        if (retriesConfig <= 0) {
            retriesConfig = DEFAULT_FAILBACK_TIMES;
        }
        int failbackTasksConfig = getUrl().getParameter(FAIL_BACK_TASKS_KEY, DEFAULT_FAILBACK_TASKS);
        if (failbackTasksConfig <= 0) {
            failbackTasksConfig = DEFAULT_FAILBACK_TASKS;
        }
        retries = retriesConfig;
        failbackTasks = failbackTasksConfig;
    }

    // 2、FailbackClusterInvoker 的 addFailed 方法
    private void addFailed(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker) {
        if (failTimer == null) {
            synchronized (this) {
                if (failTimer == null) {
                    // 创建线程 factory,主要用来自定义线程名称
                    failTimer = new HashedWheelTimer(
                            new NamedThreadFactory("failback-cluster-timer", true),
                            1,
                            TimeUnit.SECONDS, 32, failbackTasks);
                }
            }
        }
        // 创建重试任务,每 5 秒执行一次
        RetryTimerTask retryTimerTask = new RetryTimerTask(loadbalance, invocation, invokers, lastInvoker, retries, RETRY_FAILED_PERIOD);
        try {
            failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);
        } catch (Throwable e) {
            // 调用失败,只输出异常日志
            logger.error("Failback background works error,invocation->" + invocation + ", exception: " + e.getMessage());
        }
    }

    // 1、FailbackClusterInvoker 的 doInvoke 方法
    @Override
    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        Invoker<T> invoker = null;
        try {
            checkInvokers(invokers, invocation);
            // 调用 AbstractClusterInvoker 的 select方法选择 Invoker
            invoker = select(loadbalance, invocation, invokers, null);
            // 调用 Invoker 的 invoke 方法
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            // 调用失败时,只输出异常
            logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
                    + e.getMessage() + ", ", e);
            // 记录调用信息,以便重试
            addFailed(loadbalance, invocation, invokers, invoker);
            return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
        }
    }

    @Override
    public void destroy() {
        super.destroy();
        if (failTimer != null) {
            failTimer.stop();
        }
    }

    /**
     * RetryTimerTask
     */
    private class RetryTimerTask implements TimerTask {
        private final Invocation invocation;
        private final LoadBalance loadbalance;
        private final List<Invoker<T>> invokers;
        private final int retries;
        private final long tick;
        private Invoker<T> lastInvoker;
        private int retryTimes = 0;

        RetryTimerTask(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker, int retries, long tick) {
            this.loadbalance = loadbalance;
            this.invocation = invocation;
            this.invokers = invokers;
            this.retries = retries;
            this.tick = tick;
            this.lastInvoker=lastInvoker;
        }

        // 3、FailbackClusterInvoker 的 RetryTimerTask 的 run 方法
        @Override
        public void run(Timeout timeout) {
            try {
                // 调用 AbstractClusterInvoker 的 select方法选择 Invoker
                Invoker<T> retryInvoker = select(loadbalance, invocation, invokers, Collections.singletonList(lastInvoker));
                lastInvoker = retryInvoker;
                // 调用 retryInvoker 的 invoke 方法
                retryInvoker.invoke(invocation);
            } catch (Throwable e) {
                // 调用失败,输出异常日志
                logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
                if ((++retryTimes) >= retries) {
                    // 超出重试次数,输出错误日志
                    logger.error("Failed retry times exceed threshold (" + retries + "), We have to abandon, invocation->" + invocation);
                } else {
                    // 没有超出重试次数,继续加入重试任务重试
                    rePut(timeout);
                }
            }
        }

        private void rePut(Timeout timeout) {
            if (timeout == null) {
                return;
            }

            Timer timer = timeout.timer();
            if (timer.isStop() || timeout.isCancelled()) {
                return;
            }

            timer.newTimeout(timeout.task(), tick, TimeUnit.SECONDS);
        }
    }
}

FailbackClusterInvoker 的逻辑注释中已经分析的很清楚了,这里不在赘述

3.2.5  ForkingClusterInvoker

ForkingClusterInvoker 会在运行时通过线程池创建多个线程,并发调用多个服务提供者,只要有一个服务提供者成功返回了结果,doInvoke 方法就会立即结束运行

适用场景:实时性要求比较高读操作(注意是读操作,并行写操作可能不安全)下使用但这将会耗费更多的资源

源码如下:

public class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> {

    /**
     * Use {@link NamedInternalThreadFactory} to produce {@link org.apache.dubbo.common.threadlocal.InternalThread}
     * which with the use of {@link org.apache.dubbo.common.threadlocal.InternalThreadLocal} in {@link RpcContext}.
     */
    // 创建线程池
    private final ExecutorService executor = Executors.newCachedThreadPool(
            new NamedInternalThreadFactory("forking-cluster-timer", true));

    public ForkingClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            checkInvokers(invokers, invocation);
            final List<Invoker<T>> selected;
            // 获取 forks 配置,默认 2
            final int forks = getUrl().getParameter(FORKS_KEY, DEFAULT_FORKS);
            // 获取 timeout 配置,默认 1000
            final int timeout = getUrl().getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
            // 如果 forks 配置不合理,则直接将 invokers 赋值给 selected
            if (forks <= 0 || forks >= invokers.size()) {
                selected = invokers;
            } else {
                selected = new ArrayList<>();
                // 循环选出 forks 个 Invoker,并添加到 selected 中,为并行执行多个 invoker 的 invoke 方法做准备
                for (int i = 0; i < forks; i++) {
                    // 调用 AbstractClusterInvoker 的 select方法选择 Invoker
                    Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
                    // selected 中不包含当前 invoker 时,添加到 selected 中
                    if (!selected.contains(invoker)) {
                        //Avoid add the same invoker several times.
                        selected.add(invoker);
                    }
                }
            }
            RpcContext.getContext().setInvokers((List) selected);
            final AtomicInteger count = new AtomicInteger();
            final BlockingQueue<Object> ref = new LinkedBlockingQueue<>();
            for (final Invoker<T> invoker : selected) {
                // 为每个 invoker 创建一个执行线程
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 调用 Invoker 的 invoke 方法
                            Result result = invoker.invoke(invocation);
                            // 将执行结果存入阻塞队列中
                            ref.offer(result);
                        } catch (Throwable e) {
                            // 调用异常时,计数+1
                            int value = count.incrementAndGet();
                            // 如果计数大于等于 selected 的数量,表明所有的 invoker 执行都是异常的,最终将异常信息存入阻塞队列中返回
                            if (value >= selected.size()) {
                                ref.offer(e);
                            }
                        }
                    }
                });
            }
            try {
                // 从阻塞队列中取出执行结果
                Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
                // 如果结果类型为 Throwable,则抛出异常
                if (ret instanceof Throwable) {
                    Throwable e = (Throwable) ret;
                    throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);
                }
                // 返回结果
                return (Result) ret;
            } catch (InterruptedException e) {
                throw new RpcException("Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e);
            }
        } finally {
            // clear attachments which is binding to current thread.
            RpcContext.getContext().clearAttachments();
        }
    }
}

ForkingClusterInvoker 的 doInvoke 方法主要实现了以下逻辑:

1)、根据 forks 配置循环选出 forks 个 Invoker,并添加到 selected 中,为并行执行多个 invoker 的 invoke 方法做准备

2)、线程池为每个 invoker 创建一个执行线程,调用 invoker 的 invoke 方法,将执行结果存入阻塞队列中

3)、从阻塞队列中取出执行结果返回

大家想一下为什么要在 value >= selected.size()的情况下,才将异常对象添加到阻塞队列中呢?

答案:在并行调用多个服务提供者的情况下,只要有一个服务提供者能够成功返回结果,而其他全部失败。此时 ForkingClusterInvoker 仍然应该返回成功的结果,而非抛出异常。在value >= selected.size()时将异常对象放入阻塞队列中,可以保证异常对象不会出现在正常结果的前面,这样可从阻塞队列中优先取出正常的结果

3.2.6  BroadcastClusterInvoker

BroadcastClusterInvoker 会逐个调用每个服务提供者,如果其中一台报错,在循环调用结束后,BroadcastClusterInvoker 会抛出异常

适用场景:用于通知所有提供者更新缓存或日志等本地资源信息

源码如下:

public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {

    private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class);

    public BroadcastClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        checkInvokers(invokers, invocation);
        RpcContext.getContext().setInvokers((List) invokers);
        RpcException exception = null;
        Result result = null;
        // 遍历 Invoker 列表,逐个调用
        for (Invoker<T> invoker : invokers) {
            try {
                result = invoker.invoke(invocation);
            } catch (RpcException e) {
                // 记录异常
                exception = e;
                logger.warn(e.getMessage(), e);
            } catch (Throwable e) {
                // 记录异常
                exception = new RpcException(e.getMessage(), e);
                logger.warn(e.getMessage(), e);
            }
        }
        // 只要有异常,则抛出异常信息
        if (exception != null) {
            throw exception;
        }
        return result;
    }

}

BroadcastClusterInvoker 的 doInvoke 方法逻辑很简单,这里不在赘述

总结

本文中共介绍了常用的六种集群容错方式,集群容错对于 dubbo 来说挺重要的。集群模块处于 provider 和 consumer 之间,对于consumer 来说,集群可向其屏蔽 provider 集群的情况,使其能够专心进行远程调用。除此之外,通过集群模块,我们还可以对服务之间的调用链路进行编排优化,治理服务

参考:

https://dubbo.apache.org/zh-cn/docs/source_code_guide/cluster.html

猜你喜欢

转载自blog.csdn.net/ywlmsm1224811/article/details/103063115
今日推荐