深度解析dubbo集群之失败重试实现

本文基于dubbo 2.6.x

1. 介绍

在《深度解析dubbo集群之抽象实现》文中我们讲解了集群invoker的公共抽象类AbstractClusterInvoker,它主要是实现类invoke方法,与select方法(从服务提供者列表中获取一个合适的服务提供者供本次调用),今天我们再来看下它的一个实现类FailoverClusterInvoker,FailoverClusterInvoker类继承AbstractClusterInvoker抽象类,实现了其doInvoke方法,主要是实现类失败重试的功能,我们先来解释下失败重试,就是比如说我们在调用的过程中,发生了非业务异常(比如服务提供者挂了,网络波动造成超时等等),这时候会再次请求别的服务提供者进行调用,这就是失败重试,接下来我们看看FailoverClusterInvoker是怎样实现的。

2. FailoverClusterInvoker源码解析

这里源码不是很多,我就直接把源码拿过来了。

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

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

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

    /**
     * 进行调用
     * @param invocation 封装调用信息实体
     * @param invokers 服务提供者invoker集合
     * @param loadbalance 具体某个负载均衡策略
     * @return 执行结果
     * @throws RpcException
     */
    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        List<Invoker<T>> copyinvokers = invokers;
        checkInvokers(copyinvokers, invocation);// 检验参数

        // 获取重试次数,如果没有设置,就是用默认2+1  加1 操作是本身要调用一次,然后如果失败了 再重试调用1次
        int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.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();  //检验是否已经销毁
                copyinvokers = list(invocation);  // 获取所有的invokers
                // check again
                checkInvokers(copyinvokers, invocation);
            }
            // 选择一个invoker
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
            invoked.add(invoker);  // 添加到已经选择的列表中
            // 将已经选择过的invoker列表设置到context中
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                // 调用 获得结果
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {//重试过程中,将异常信息以 warn 级别日志输出
                    logger.warn("Although retry the method " + invocation.getMethodName()
                            + " 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;  // 其他性质的异常统一封装成RpcException
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {

                // 将提供者的地址添加到providers
                providers.add(invoker.getUrl().getAddress());
            }
        }
        // 最后抛出异常
        throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
                + invocation.getMethodName() + " 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 != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
    }

}

首先是检验 服务提供者列表 是不是空,也就是checkInvokers方法,获取请求重试次数的参数值,这个用户是可以配置的,如果没有配置默认是2次,再加上这次请求一共是3次。定义一个invoked 的集合,用来存放调用过的invoker,然后进行for循环,一共循环 重试次数+1 次,这里也就是3次, 在循环中, i>0也就是第二次以上的请求,都会检查一下是否销毁,重新获取服务提供者列表,检验服务提供者列表(因为第一次请求期间服务提供者列表会发生变化,或者服务正在销毁中)。调用父类的select 方法选择一个合适的服务提供者invoker,将选择出来的invoker 加入到invoked 集合中,这个invoked集合是存放已经选过的(这里为什么要这样做呢,一旦出现非业务异常,下次重试的时候就尽量不再选这个invoker,毕竟出现过非业务异常,可能是存在服务不可用了等等问题)将invoked 放到context中,接着就是进行调用了,正常调用就返回结果,出现异常,如果是业务异常直接抛出(业务异常说明本次调用成功了),如果不是业务异常,就统一封装成RpcException。不是业务异常就进行重试,for循环继续,如果到最后还没有成功返回,就抛出RpcException 异常,将最后一次捕获的异常信息抛出去。
好了,到这咱们的FailoverClusterInvoker源码就解析完了,还是很简单的。

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