深度解析dubbo集群之并发调用与安全失败实现

本文基于dubbo v2.6.x

1.介绍

本文将解析下dubbo 集群的两个实现,分别是并发请求与安全失败,首先说下这个并发请求,并发请求就是将同一个请求信息使用多线程发送到多个不同的服务提供者上,然后获取第一个响应回来的结果,其实就是并发调用服务提供者们,取最快响应回来的那个结果。再来说下这个安全失败(实在找不到合适的词语),在《深度解析dubbo集群之快速失败与广播调用实现》 一文中,我们讲过快速失败,这个快速失败就是调用一次,如果异常就抛出异常,而这个安全失败与快速失败差不多,不过在异常处理上有差距,安全失败在遇到异常的时候,只是打印error日志,返回一个空的结果值。解析来我们分别看下它们的源码实现。

2.FailsafeClusterInvoker

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

            // 从invokers 选择一个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(); // ignore
        }
    }
}

可以看出,FailsafeClusterInvoker继承AbstractClusterInvoker 抽象类,然后实现doInvoke方法,直接看下doInvoke方法里面,先是检查服务提供者们,接着就是调用AbstractClusterInvoker 的list方法,选择一个合适的invoker(也就是服务提供者),进行调用,如果出现异常,则打印error级别的日志,返回一个空的结果值。

3.ForkingClusterInvoker

ForkingClusterInvoker这个类就是并发请求的实现了,我们来看下具体的源码

/**
 * Invoke a specific number of invokers concurrently, usually used for demanding real-time operations, but need to waste more service resources.
 *
 * <a href="http://en.wikipedia.org/wiki/Fork_(topology)">Fork</a>
 *  并发调用特定数量的调用器,通常用于要求实时操作,但需要浪费更多的服务资源。
 *
 *
 *  并发请求,获取那个最快响应回来的
 */
public class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> {

    /**
     * Use {@link NamedInternalThreadFactory} to produce {@link com.alibaba.dubbo.common.threadlocal.InternalThread}
     * which with the use of {@link com.alibaba.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 {
            // 检查invokers 是否为空
            checkInvokers(invokers, invocation);
            // 记录选中的invoker们
            final List<Invoker<T>> selected;
            // forks 缺省2
            final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS);

            //timeout 缺省1s
            final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
            // forks 小于等于0  或者 forks 大于的 invoker的列表
            if (forks <= 0 || forks >= invokers.size()) {
                selected = invokers;//使用全部的invokers
            } else {
                selected = new ArrayList<Invoker<T>>();
                for (int i = 0; i < forks; i++) {// 选择几个invoker
                    // TODO. Add some comment here, refer chinese version for more details.
                    Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
                    if (!selected.contains(invoker)) {//Avoid add the same invoker several times.
                        selected.add(invoker);
                    }
                }
            }
            ///---------------------------------------------------------
            // 将选中的invoker们 放到context中
            RpcContext.getContext().setInvokers((List) selected);
            final AtomicInteger count = new AtomicInteger();// 计数器
            final BlockingQueue<Object> ref = new LinkedBlockingQueue<Object>();// 队列
            for (final Invoker<T> invoker : selected) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Result result = invoker.invoke(invocation);
                            ref.offer(result);// 将结果放到队列中
                        } catch (Throwable e) {

                            // 计数器+1
                            int value = count.incrementAndGet();

                            // 如果 失败次数 大于等于 选中的invoker的时候
                            if (value >= selected.size()) {
                                ref.offer(e);// 将异常放到队列中
                            }
                        }
                    }
                });
            }
            //----------------------------------------------------------------------------
            try {

                // 在超时范围内获取第一个结果
                Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
                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. 清理当前线程context中的参数值
            RpcContext.getContext().clearAttachments();
        }
    }
}

我们看到ForkingClusterInvoker 也是继承AbstractClusterInvoker 抽象类,重写doInvoke方法,我们看下doInvoke方法,这里篇幅比较长,我把doInvoke方法用“------”分成了三部分,我们分别说下
第一部分:这部分主要是根据用户配置的forks参数值来选择服务提供者们
第二部分:这部分就是使用多线程进行调用了。
第三部分:这部分主要是获取调用结果
我们详细说下:
第一部分,先是检查服务提供者们是否是空,获取用户配置的forks参数,这里缺省是2,表示默认并发调用2个服务提供者的,接下来就是选服务提供者了,如果forks是小于0的或者大于服务提供者们,这时候就是选择全部的服务提供者,否则,调用父类AbstractClusterInvoker的select方法选择forks个数的服务提供者
第二部分,就是创建一个计数器用来累加异常个数的,创建一个队列,用来存放结果集的,接着就是遍历选出来的服务提供者们放到线程池中进行调用,将调用结果放到队列中,如果异常次数>=调用次数,这时候就将最后一个异常塞到队列中(其实就是在队列头里面)。
第三部分,调用对列的poll 方法在等待时间范围内获取第一个结果,如果是异常类型,就抛出异常。最后就是清空上下文中的附加参数(这里如果是普通调用的话,到下面过滤器部分会有清除操作,但是是并发调用,当前这个线程并没有调用下面的步骤,所以需要自己清理下)

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