dubbo集群容错 - litter-fish/ReadSource GitHub Wiki

集群容错

容错方式:

Failover Cluster - 失败自动切换
Failfast Cluster - 快速失败
Failsafe Cluster - 失败安全
Failback Cluster - 失败自动恢复
Forking Cluster - 并行调用多个服务提供者

集群工作过程

第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作 第二个阶段是在服务消费者进行远程调用时 例子:FailoverClusterInvoker , 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。 当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。 最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoker 方法,进行真正的远程调用。

Cluster 和 Cluster Invoker Cluster 接口和相关实现类作用:生成 Cluster Invoker

AbstractClusterInvoker

消费者进行远程调用

public Result invoke(final Invocation invocation) throws RpcException {
    checkWhetherDestroyed();
    LoadBalance loadbalance = null;

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

    // 列举 Invoker
    List<Invoker<T>> invokers = list(invocation);
    if (invokers != null && !invokers.isEmpty()) {
        // 加载 LoadBalance
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
    }
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);

    // 调用 doInvoke 进行后续操作
    return doInvoke(invocation, invokers, loadbalance);
}

列举 Invoker

protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
    // 调用 Directory 的 list 方法列举 Invoker
    List<Invoker<T>> invokers = directory.list(invocation);
    return invokers;
}

失败自动切换

FailoverClusterInvoker(默认配置):调用失败时,会自动切换 Invoker 进行重试。

远程调用

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    List<Invoker<T>> copyinvokers = invokers;
    checkInvokers(copyinvokers, invocation);
    // 获取重试次数
    int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
    if (len <= 0) {
        len = 1;
    }
    RpcException le = null;
    List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size());
    Set<String> providers = new HashSet<String>(len);
    // 循环调用,失败重试
    for (int i = 0; i < len; i++) {
        if (i > 0) {
            checkWhetherDestroyed();
            // 在进行重试前重新列举 Invoker,这样做的好处是,如果某个服务挂了,
            // 通过调用 list 可得到最新可用的 Invoker 列表
            copyinvokers = list(invocation);
            // 对 copyinvokers 进行判空检查
            checkInvokers(copyinvokers, invocation);
        }

        // 通过负载均衡选择 Invoker
        Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
        // 添加到 invoker 到 invoked 列表中
        invoked.add(invoker);
        // 设置 invoked 到 RPC 上下文中
        RpcContext.getContext().setInvokers((List) invoked);
        try {
            // 调用目标 Invoker 的 invoke 方法
            Result result = invoker.invoke(invocation);
            return result;
        } catch (RpcException e) {
            if (e.isBiz()) {
                throw e;
            }
            le = e;
        } catch (Throwable e) {
            le = new RpcException(e.getMessage(), e);
        } finally {
            providers.add(invoker.getUrl().getAddress());
        }
    }
    // 若重试失败,则抛出异常
    throw new RpcException(..., "Failed to invoke the method ...");
}

通过负载均衡选择 Invoker

// com/alibaba/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
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();

    // 获取 sticky 配置,sticky 表示粘滞连接。所谓粘滞连接是指让服务消费者尽可能的
    // 调用同一个服务提供者,除非该提供者挂了再进行切换
    boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY,
        Constants.DEFAULT_CLUSTER_STICKY);
    {
        // 检测 invokers 列表是否包含 stickyInvoker,如果不包含,
        // 说明 stickyInvoker 代表的服务提供者挂了,此时需要将其置空
        if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
            stickyInvoker = null;
        }

        // 在 sticky 为 true,且 stickyInvoker != null 的情况下。如果 selected 包含
        // stickyInvoker,表明 stickyInvoker 对应的服务提供者可能因网络原因未能成功提供服务。
        // 但是该提供者并没挂,此时 invokers 列表中仍存在该服务提供者对应的 Invoker。
        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;
}

先处理粘滞连接,接着通过负载均衡选择 Invoker

/**
 - 作用:
 - 第一是通过负载均衡组件选择 Invoker。
 - 第二是如果选出来的 Invoker 不稳定,或不可用,此时需要调用 reselect 方法进行重选
 */
private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
    if (invokers == null || invokers.isEmpty())
        return null;
    if (invokers.size() == 1)
        return invokers.get(0);
    if (loadbalance == null) {
        // 如果 loadbalance 为空,这里通过 SPI 加载 Loadbalance,默认为 RandomLoadBalance
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
    }

    // 通过负载均衡组件选择 Invoker
    Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

    // 如果 selected 包含负载均衡选择出的 Invoker,或者该 Invoker 无法经过可用性检查,此时进行重选
    if ((selected != null && selected.contains(invoker))
            || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
        try {
            // 进行重选
            Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
            if (rinvoker != null) {
                // 如果 rinvoker 不为空,则将其赋值给 invoker
                invoker = rinvoker;
            } else {
                // rinvoker 为空,定位 invoker 在 invokers 中的位置
                int index = invokers.indexOf(invoker);
                try {
                    // 获取 index + 1 位置处的 Invoker,以下代码等价于:
                    //     invoker = invokers.get((index + 1) % invokers.size());
                    invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0);
                } catch (Exception e) {
                    logger.warn("... may because invokers list dynamic change, ignore.");
                }
            }
        } catch (Throwable t) {
            logger.error("cluster reselect fail reason is : ...");
        }
    }
    return invoker;
}

如果 selected 包含负载均衡选择出的 Invoker,或者该 Invoker 无法经过可用性检查,此时进行重选

/**
 - 作用:
 - 第一是查找可用的 Invoker,并将其添加到 reselectInvokers 集合中。
 - 第二,如果 reselectInvokers 不为空,则通过负载均衡组件再次进行选择
 */
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) {
        // 检测可用性
        if (availablecheck && !invoker.isAvailable()) {
            continue;
        }
        // 如果 selected 列表不包含当前 invoker,则将其添加到 reselectInvokers 中
        if (selected == null || !selected.contains(invoker)) {
            reselectInvokers.add(invoker);
        }
    }
    // reselectInvokers 不为空,此时通过负载均衡组件进行选择
    if (!reselectInvokers.isEmpty()) {
        return loadbalance.select(reselectInvokers, getUrl(), invocation);
    }

    // 若线程走到此处,说明 reselectInvokers 集合为空,此时不会调用负载均衡组件进行筛选。
    // 这里从 selected 列表中查找可用的 Invoker,并将其添加到 reselectInvokers 集合中
    if (selected != null) {
        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;
}

失败自动恢复

FailbackClusterInvoker:调用失败后,返回一个空结果给服务提供者。并通过定时任务对失败的调用进行重传,适合执行消息通知等操作。

FailbackClusterInvoker重要属性

private static final long RETRY_FAILED_PERIOD = 5 * 1000;

private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2,
        new NamedInternalThreadFactory("failback-cluster-timer", true));

private final ConcurrentMap<Invocation, AbstractClusterInvoker<?>> failed = new ConcurrentHashMap<Invocation, 
        AbstractClusterInvoker<?>>();
private volatile ScheduledFuture<?> retryFuture;

执行远程调用

/**
 - 首先是 doInvoker,该方法负责初次的远程调用。若远程调用失败,则通过 addFailed 方法将调用信息存入到 failed 中,等待定时重试。
 - addFailed 在开始阶段会根据 retryFuture 为空与否,来决定是否开启定时任务。
 - retryFailed 方法则是包含了失败重试的逻辑,该方法会对 failed 进行遍历,然后依次对 Invoker 进行调用。调用成功则将 Invoker 从 failed 中移除,调用失败则忽略失败原因。
 */
protected Result doInvoke(
        Invocation invocation,
        List<Invoker<T>> invokers,
        LoadBalance loadbalance) throws RpcException {
    try {
        checkInvokers(invokers, invocation);
        // 选择 Invoker
        Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
        // 进行调用
        return invoker.invoke(invocation);
    } catch (Throwable e) {
        // 如果调用过程中发生异常,此时仅打印错误日志,不抛出异常
        logger.error("Failback to invoke method ...");
        // 记录调用信息
        addFailed(invocation, this);
        // 返回一个空结果给服务消费者
        return new RpcResult();
    }
}

记录失败调用信息

private void addFailed(Invocation invocation, AbstractClusterInvoker<?> router) {
    if (retryFuture == null) {
        synchronized (this) {
            if (retryFuture == null) {
                // 创建定时任务,每隔5秒执行一次
                retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 对失败的调用进行重试
                            retryFailed();
                        } catch (Throwable t) {
                            // 如果发生异常,仅打印异常日志,不抛出
                            logger.error("Unexpected error occur at collect statistic", t);
                        }
                    }
                }, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS);
            }
        }
    }
    // 添加 invocation 和 invoker 到 failed 中
    failed.put(invocation, router);
}

对失败的调用进行重试

void retryFailed() {
    if (failed.size() == 0) {
        return;
    }
    // 遍历 failed,对失败的调用进行重试
    for (Map.Entry<Invocation, AbstractClusterInvoker<?>> entry : new HashMap<Invocation, AbstractClusterInvoker<?>>(failed).entrySet()) {
        Invocation invocation = entry.getKey();
        Invoker<?> invoker = entry.getValue();
        try {
            // 再次进行调用
            invoker.invoke(invocation);
            // 调用成功后,从 failed 中移除 invoker
            failed.remove(invocation);
        } catch (Throwable e) {
            // 仅打印异常,不抛出
            logger.error("Failed retry to invoke method ...");
        }
    }
}

快速失败

FailfastClusterInvoker:只会进行一次调用,失败后立即抛出异常。适用于幂等操作,比如新增记录。

执行远程调用

/**
 - 首先是通过 select 方法选择 Invoker,然后进行远程调用。如果调用失败,则立即抛出异常
 */
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    checkInvokers(invokers, invocation);
    // 选择 Invoker
    Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
    try {
        // 调用 Invoker
        return invoker.invoke(invocation);
    } catch (Throwable e) {
        if (e instanceof RpcException && ((RpcException) e).isBiz()) {
            // 抛出异常
            throw (RpcException) e;
        }
        // 抛出异常
        throw new RpcException(..., "Failfast invoke providers ...");
    }
}

失败安全

FailsafeClusterInvoker:失败安全的 Cluster Invoker。所谓的失败安全是指,当调用过程中出现异常时, FailsafeClusterInvoker 仅会打印异常,而不会抛出异常。适用于写入审计日志等操作

执行远程调用

public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    try {
        checkInvokers(invokers, invocation);
        // 选择 Invoker
        Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
        // 进行远程调用
        return invoker.invoke(invocation);
    } catch (Throwable e) {
        // 打印错误日志,但不抛出
        logger.error("Failsafe ignore exception: " + e.getMessage(), e);
        // 返回空结果忽略错误
        return new RpcResult();
    }
}

并行调用多个服务提供者

ForkingClusterInvoker:在运行时通过线程池创建多个线程,并发调用多个服务提供者。只要有一个服务提供者成功返回了结果,doInvoke 方法就会立即结束运行。 ForkingClusterInvoker 的应用场景是在一些对实时性要求比较高读操作(注意是读操作,并行写操作可能不安全)下使用,但这将会耗费更多的资源

ForkingClusterInvoker 重要属性

// 初始化一个缓存连接池
private final ExecutorService executor = Executors.newCachedThreadPool(
        new NamedInternalThreadFactory("forking-cluster-timer", true));

执行远程调用

public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
        throws RpcException {
    try {
        /**
         - 用于选出 forks 个 Invoker,为接下来的并发调用提供输入。
         */
        checkInvokers(invokers, invocation);
        final List<Invoker<T>> selected;
        // 获取 forks 配置
        final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS);
        // 获取超时配置
        final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
        // 如果 forks 配置不合理,则直接将 invokers 赋值给 selected
        if (forks <= 0 || forks >= invokers.size()) {
            selected = invokers;
        } else {
            selected = new ArrayList<Invoker<T>>();
            // 循环选出 forks 个 Invoker,并添加到 selected 中
            for (int i = 0; i < forks; i++) {
                // 选择 Invoker ???? 如果选择的结果包括在selected中了,最终个数结果是否小于forks?
                Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
                if (!selected.contains(invoker)) {
                    selected.add(invoker);
                }
            }
        }
        // ----------------------✨ 分割线1 ✨---------------------- //
        /**
         - 通过线程池并发调用多个 Invoker,并将结果存储在阻塞队列中
         */
        RpcContext.getContext().setInvokers((List) selected);
        // 如果调用失败则计数器加一
        final AtomicInteger count = new AtomicInteger();
        final BlockingQueue<Object> ref = new LinkedBlockingQueue<Object>();
        // 遍历 selected 列表
        for (final Invoker<T> invoker : selected) {
            // 为每个 Invoker 创建一个执行线程
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 进行远程调用
                        Result result = invoker.invoke(invocation);
                        // 将结果存到阻塞队列中
                        ref.offer(result);
                    } catch (Throwable e) {
                        int value = count.incrementAndGet();
                        // 仅在 value 大于等于 selected.size() 时,才将异常对象
                        // 放入阻塞队列中,在并行调用多个服务提供者的情况下,只要有一个服务提供者能够成功返回结果,而其他全部失败。
                        // 此时 ForkingClusterInvoker 仍应该返回成功的结果,而非抛出异常。
                        if (value >= selected.size()) {
                            // 将异常对象存入到阻塞队列中
                            ref.offer(e);
                        }
                    }
                }
            });
        }
        // ----------------------✨ 分割线2 ✨---------------------- //
        /**
         - 用于从阻塞队列中获取返回结果,并对返回结果类型进行判断。如果为异常类型,则直接抛出,否则返回。
         */
        try {
            // 从阻塞队列中取出远程调用结果
            Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
            // 如果结果类型为 Throwable,则抛出异常
            if (ret instanceof Throwable) {
                Throwable e = (Throwable) ret;
                throw new RpcException(..., "Failed to forking invoke provider ...");
            }
            // 返回结果
            return (Result) ret;
        } catch (InterruptedException e) {
            throw new RpcException("Failed to forking invoke provider ...");
        }
    } finally {
        RpcContext.getContext().clearAttachments();
    }
}

广播调用多个服务提供者

BroadcastClusterInvoker:逐个调用每个服务提供者,如果其中一台报错,在循环调用结束后,BroadcastClusterInvoker 会抛出异常。 该类通常用于通知所有提供者更新缓存或日志等本地资源信息

执行远程调用

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);
        }
    }

    // exception 不为空,则抛出异常
    if (exception != null) {
        throw exception;
    }
    return result;
}
⚠️ **GitHub.com Fallback** ⚠️