深度解析dubbo集群之失败恢复

本文基于dubbo v2.6.x

1. 介绍

本文主要讲解dubbo集群失败恢复功能的实现,在文章《深度解析dubbo集群之API》讲解了关于FailbackCluster的源码,在join方法实现中就是创建了一个FailbackClusterInvoker 返回,具体源码看下图:
在这里插入图片描述
在FailbackClusterInvoker 类中,是对调用过程中失败恢复功能的实现,在这里稍微说下dubbo是怎样做的失败恢复,在服务调用者进行远程调用的时候,如果没出现异常还好,直接返回就行,出现异常的时候,它会把这次调用信息添加到map集合中,然后有一个定时任务每隔 上次任务执行时间+5秒进行重新调用,成功了就移除那个定时任务。

2. 配置

在服务调用者端:
注解形式:

@Reference(cluster ="failback" )

xml形式:

   <dubbo:reference interface="com.xuzhaocai.dubbo.provider.IHelloProviderService" id="iHelloProviderService"  cluster="failback"></dubbo:reference>

3. FailbackClusterInvoker 源码解析

首先我们看下FailbackClusterInvoker 类定义

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

FailbackClusterInvoker 继承AbstractClusterInvoker 抽象类,这里是肯定要实现doInvoke 方法了。

 @Override
    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {

            // 检查 invoker 是否为空
            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 " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
                    + e.getMessage() + ", ", e);

            // 添加失败
            addFailed(invocation, this);
            // 这里直接返回一个空的RpcResult 结果
            return new RpcResult(); // ignore
        }
    }

在doInvoke方法中,先是检查服务提供者们是否为空,接着就是调用父类select 方法选择一个合适的invoker(服务提供者) 进行调用,没出现异常的话,正常返回,如果出现异常,先是打印错误日志,接着就是调用addFailed 方法添加到失败的集合中,最后返回一个空的结果。
接着看下addFailed 方法的实现:

// 使用定时任务进行重试
    private void addFailed(Invocation invocation, AbstractClusterInvoker<?> router) {
        if (retryFuture == null) {
            synchronized (this) {
                if (retryFuture == null) {
                    retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {

                        @Override
                        public void run() {
                            // collect retry statistics
                            try {

                                // 重试
                                retryFailed();
                            } catch (Throwable t) { // Defensive fault tolerance
                                logger.error("Unexpected error occur at collect statistic", t);
                            }
                        }
                    }, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS);
                }
            }
        }

        // 添加到失败集合中
        failed.put(invocation, router);
    }

在addFailed 方法中如果retryFuture这个重试任务是空的话就创建,我们可以看到scheduleWithFixedDelay的4个参数分别是:

  1. Runnable的匿名实现
  2. 初始换延迟时间 RETRY_FAILED_PERIOD = 5s 这个参数就是这个任务初始化延迟的一个时间
  3. 延迟间隔时间 RETRY_FAILED_PERIOD =5s 任务延迟间隔时间
  4. 单位MILLISECONDS
    任务执行周期计算 = 任务耗时时间+延迟间隔时间
    我们可以看到任务执行调用的是 retryFailed(); 重试方法。
    最后将失败的调用信息添加到了map集合中,我们看下这个map集合的定义。
private final ConcurrentMap<Invocation, AbstractClusterInvoker<?>> failed = new ConcurrentHashMap<Invocation, AbstractClusterInvoker<?>>();

在failed集合中 ,key就是调用信息invocation 对应的value就是当前的FailbackClusterInvoker 对象。
接着再来看下重试方法retryFailed的源码实现:

  void retryFailed() {
        if (failed.size() == 0) {
            return;
        }
        // 遍历重试
        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.remove(invocation);
            } catch (Throwable e) {
                logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
            }
        }
    }

这里就是遍历失败集合元素,进行重新调用,我们看到这个失败调用就没有对返回值的处理了,所以说这个失败恢复不适合普通的业务调用,用它能够实现最大努力通知。调用成功 就从失败集合中移除,调用异常打印error日志。
好了,到这我们的失败恢复就讲解完成了。

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