Dubbo之Cluster集群容错

Dubbo版本

  1. Dubbo版本2.6.7

集群作用

  1. Dubbo 定义了集群接口 Cluster 以及 Cluster Invoker。集群 Cluster 用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者。服务消费者只需通过这个 Invoker 进行远程调用即可,至于具体调用哪个服务提供者,以及调用失败后如何处理等问题,现在都交给集群模块去处理。

  2. 集群模块(Cluster)是服务提供者和服务消费者的中间层,为服务消费者屏蔽了服务提供者的情况,这样服务消费者就可以专心处理远程调用相关事宜。比如发请求,接受服务提供者返回的数据等。这就是集群的作用。

  3. Dubbo集群层(Cluster层)的总体工作流程

    • 生成Invoker对象。
    • 筛选可用的服务列表。通过Directory#list获取所有可用的服务列表。根据不同的ClusterInvoker筛选出不同的Invoker对象,使用Router处理改服务列表,根据路由规则过滤服务列表,最终返回剩余的服务列表
    • 做负载均衡。筛选可用的服务列表后,通过不同的负载均衡策略选择出一个服务,然后调用
    • 做RPC调用。

    Cluster层流程

集群容错方式

  1. Dubbo提供的集群容错方式

    • Failover Cluster : 失败自动切换重试,当服务消费方调用服务提供者失败后,会自动切换到其他服务提供者服务器进行重试,这通常用于读操作或者具有幂等的写操作。需要注意的是,重试会带来更长延迟。可以通过retries="2"来设置重试次数,表示额外的重试2次
    • **Failfast Cluster **:快速失败,当服务消费方调用服务提供者失败后,立即报错,即只调用一次。通常,这种模式用于非幂等性的写操作。
    • Failsafe Cluster : 失败安全,当服务消费者调用服务出现异常时,直接忽略异常。这种模式一般用于写入日志等操作,即不关心失败
    • Failback Cluster: 失败自动恢复,当服务消费端调用服务出现异常后,在后台记录失败的请求,并按照一定的策略后期再进行重试。这种模式通常用于消息通知操作
    • Forking Cluster:并行调用多个服务提供者。当消费方调用一个接口方法后,消费者会并行调用多个服务提供者的服务,只要其中有一个成功即返回。这种模式通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="4"来设置最大并行数
    • Broadcast Cluster:广播调用,当消费者调用一个接口方法后,Dubbo消费者会逐个调用所有服务提供者,任意一台服务器调用异常则这次调用就标志失败。这种模式通常用于通知所有提供者更新缓存或日志等本地资源信息。由于是广播,所以不需要做负载均衡
    • Available Cluster:选择一个可以使用的服务提供者进行调用
    • Mergeable Cluster:依次调用所有invokers, 并通过使用一个merger进行结果合并处理以返回结果。

自定义集群容错策略

  1. 除了以上默认提供的容错方式,如果有定制化需求,可以根据Dubbo提供的扩展接口Cluster进行定制

  2. 实现Cluster接口

    package com.alibaba.dubbo.spi;
    
    import com.alibaba.dubbo.rpc.Invoker;
    import com.alibaba.dubbo.rpc.RpcException;
    import com.alibaba.dubbo.rpc.cluster.Cluster;
    import com.alibaba.dubbo.rpc.cluster.Directory;
    
    public class LogCluster implements Cluster {
        @Override
        public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
            return new LogClusterInvoker<>(directory);
        }
    }
  3. 创建LogClusterInvoker

    public class LogClusterInvoker<T> extends AbstractClusterInvoker<T> {
        private static final Logger logger = LoggerFactory.getLogger(LogClusterInvoker.class);
    
        public LogClusterInvoker(Directory<T> directory) {
            super(directory);
        }
    
        @Override
        protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers,
                                  LoadBalance loadbalance) throws RpcException {
    
            checkInvokers(invokers, invocation);
            //使用负载均衡策略选择一个服务提供者
            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
            logger.info(String.format("methodName:%s,url:%s,attachments:%s",
                    invocation.getMethodName(), invoker.getUrl(), invocation.getAttachments()));
            try {
                //执行远程调用
    
                Result result = invoker.invoke(invocation);
                logger.info(String.format("result:%s,attachments:%s", result.getValue(), result.getAttachments()));
                return result;
            } catch (Throwable e) {
                if (e instanceof RpcException && ((RpcException) e).isBiz()) {
                    throw (RpcException) e;
                }
                throw new RpcException(e);
            }
        }
    }
  4. src/main/resources/META-INF/dubbo目录下创建com.alibaba.dubbo.rpc.cluster.Cluster文件,内容如下

    log=com.alibaba.dubbo.spi.LogCluster
  5. 在Consumer端配置

    <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService" cluster="log"/>

集群配置

  1. 集群模式配置

    <dubbo:service cluster="failsafe" />
    <dubbo:reference cluster="failsafe" />

重试次数

  1. Dubbo 服务在尝试调用一次之后,如出现非业务异常(服务突然不可用、超时等),Dubbo 默认(Failover Cluster策略)会进行额外的最多2次重试(即总共最多3次调用)。重试次数的源码分析在下面的FailoverClusterInvoker源码分析处

  2. 重试次数支持两种自定义配置:

    • 通过注解/xml进行固定配置
    • 通过上下文进行运行时动态配置
  3. 通过注解/xml进行固定配置

    <dubbo:service retries="2" />
    <dubbo:consumer retries="2" />
    <dubbo:reference retries="2" />
    <dubbo:reference>
        <dubbo:method name="findFoo" retries="2" />
    </dubbo:reference>
  4. 通过RpcContext进行运行时动态配置,优先级高于注解/xml进行的固定配置(两者都配置的情况下,以RpcContext配置为准).

    // dubbo服务调用前,通过RpcContext动态设置本次调用的重试次数
    RpcContext rpcContext = RpcContext.getContext();
    rpcContext.setAttachment("retries", 5);
  5. 如果指定的是一个负数或者0,则设置为1

    //源码部分,默认重试次数是2
    Constants#DEFAULT_RETRIES=2  
    //如果指定的是一个负数或者0,则设置为1  
    int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
    if (len <= 0) {
        len = 1;
    }

Cluster源码分析

  1. Cluster 是接口,而 Cluster Invoker 是一种 Invoker。服务提供者的选择逻辑,以及远程调用失败后的的处理逻辑均是封装在 Cluster Invoker 中。 Cluster 接口仅用于生成 Cluster Invoker

Cluster

  1. FailoverCluster为例

    public class FailoverCluster implements Cluster {
    
        public final static String NAME = "failover";
    
        @Override
        public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
            return new FailoverClusterInvoker<T>(directory);
        }
    
    }

ClusterInvoker源码分析

  1. 集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间。第二个阶段是在服务消费者进行远程调用时,此时 AbstractClusterInvoker 的 invoke 方法会被调用

    Invoker

  2. AbstractClusterInvoker#invoke:主要用于获取所有的 Invoker,以及加载 LoadBalance。最后再调用模板方法 doInvoke 进行后续操作

    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();
        LoadBalance loadbalance = null;
    
        // binding attachments into invocation.
        // 绑定 attachments 到 invocation 中.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
        //从RegistryDirectory管理的RouterChain的route()方法中获取保存的invoker列表
        List<Invoker<T>> invokers = list(invocation);
        if (invokers != null && !invokers.isEmpty()) {
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
        }
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        //子类实现具体的逻辑
        return doInvoke(invocation, invokers, loadbalance);
    }

FailsafeClusterInvoker

  1. 当服务消费者调用服务出现异常时,直接忽略异常

    @Override
    public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            checkInvokers(invokers, invocation);
            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
        }
    }

FailoverClusterInvoker

  1. 失败自动切换重试,当服务消费方调用服务提供者失败后,会自动切换到其他服务提供者服务器进行重试

    • 检查通过负载均衡策略获取的服务者是否可用
    • 获取重试次数,默认是2次如果是负数或者0,则设置为1
    • 如果第一次调用成功,则直接返回结果
    • 如果第一次调用失败,则重试,此时需要重新获取服务列表(可能发生了变化)
    @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
        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);
                // check again 再次检查服务提供者是否为空
                checkInvokers(copyinvokers, invocation);
            }
            //选择负载均衡策略
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
            invoked.add(invoker);
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                //发起远程调用
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                    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;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
                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);
    }

FailbackClusterInvoker

  1. 失败自动恢复,当服务消费端调用服务出现异常后,在后台记录失败的请求,并按照一定的策略后期再进行重试

    • 检查服务列表invokers是否为空
    • 根据负载均衡策略选择一个invoker
    • 远程调用,如果失败,则把当前上下文添加到定时器中.并返回一个空的结果
    @Override
    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            //检查invokers是否合法,及是否为空
            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);
            return new RpcResult(); // ignore
        }
    }
  2. 添加到定时器的方法addFailed

    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() {
                            // 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);
    }
  3. 重试任务retryFailed方法

    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 " + invocation.getMethodName() + ", waiting again.", e);
            }
        }
    }

ForkingClusterInvoker

  1. 并行调用多个服务提供者。当消费方调用一个接口方法后,消费者会并行调用多个服务提供者的服务,只要其中有一个成功即返回

    • 获取fork=xx配置,默认是2。如果fork配置超出边界,则以服务列表数量为准,否则,遍历fork数量,选择fork数量的列表
    • 遍历选择的服务列表,使用线程池进行并行调用,并将成功结果存储到阻塞队列中。如果所有结果都发生异常,则会将最后一个异常存储到阻塞队列中
    • 这里需要注意的是,即使ref.poll返回了结果,其他线程还是会继续执行(继续远程调用)
    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(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++) {
                    // 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);
                    }
                }
            }
            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() 时,才将异常对象
                            //可以保证异常对象不会出现在正常结果的前面,这样可从阻塞队列中优先取出正常的结果。
    												//如果所有的都发生异常,就只有一个异常会存在队列里,只要有一个成功,队列里就会有一个正确的结果
                            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();
        }
    }

BroadcastClusterInvoker

  1. BroadcastCluster:广播调用,当消费者调用一个接口方法后,Dubbo消费者会逐个调用所有服务提供者,任意一台服务器调用异常则这次调用就标志失败,只有最后一个异常回被抛出

    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;
        //循环调用所有的服务列表,如果其中有一个出现异常,则抛出异常
        //如果没有出现异常,则返回最后一个服务提供者的返回结果
        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;
    }

AvailableClusterInvoker

  1. AvailableClusterInvoker:选择一个可以使用的Invoke调用

    public class AvailableClusterInvoker<T> extends AbstractClusterInvoker<T> {
    
        public AvailableClusterInvoker(Directory<T> directory) {
            super(directory);
        }
    
        @Override
        public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
          	//选择一个可使用的进行调用
            for (Invoker<T> invoker : invokers) {
                if (invoker.isAvailable()) {
                    return invoker.invoke(invocation);
                }
            }
            throw new RpcException("No provider available in " + invokers);
        }
    
    }

MergeableClusterInvoker

  1. MergeableClusterInvoker 容错,依次调用所有invokers, 并通过使用一个merger进行结果合并处理以返回结果。

    1. 搜搜所有分组,根据返回结果的类型自动查找合并器。该接口中getMenuItems方法不做合并
    <dubbo:reference id="demoService" 
                     group="*"
                     merger="true"
                     interface="com.alibaba.dubbo.demo.DemoService">
      <dubbo:method name="getMenuItems" meger="false"/>
    </dubbo:reference>  
    2. 指定方法合并结果
    <dubbo:reference id="demoService" 
                     group="*"
                     interface="com.alibaba.dubbo.demo.DemoService">
      <dubbo:method name="getMenuItems" meger="mymerge"/>
    </dubbo:reference>  
    3. 调用返回结果的指定方法进行合并
    <dubbo:reference id="demoService" 
                     group="*"
                     interface="com.alibaba.dubbo.demo.DemoService">
      <dubbo:method name="getMenuItems" meger=".addAll"/>
    </dubbo:reference>
  2. 当一个接口有多种实现,消费者又需要同时引用不同的实现时,可以用group 来区分不同的实现。如果我们需要并行调用不同group 的服务,并且要把结果集合并起来,则需要用到Merger特性。Merger实现了多个服务调用后结果合并的逻辑。虽然业务层可以自行实现这个能力,但Dubbo直接封装到框架中,作为一种扩展点能力,简化了业务开发的复杂度

  3. MergeableClusterInvoker并没有继承AbstractClusterInvoker,而是直接实现了Invoker接口

    • 获取所有的Invoker服务列表
    • 判断方法是否有合并器,如果没有,则不会并行调用多个group,找到第一个可以调用的Invoker直接调用就返回了。如果有合井器则继续执行
    • 获取接口的返回类型。通过反射获得返回类型,后续要根据这个返回值查找不同的合并器
    • 使用线程异步调用所有的服务列表,阻塞获取结果,将没有异常的结果放到结果列表中
    • 如果结果列表为空,则直接返回一个空结果。如果只有一个结果,则直接返回。如果返回值是void,也直接返回
    • 合并结果集。如果配置的是merger=".addAll",则直接通过反射调用返回类型中的addAll方法合并结果集。例如:返回类型是Set,则调用Set.addAll来合并结果
    • 合并有两种方式,根据配置不同可用分为基于方法的合并和基于merger的合并
    @Override
    @SuppressWarnings("rawtypes")
    public Result invoke(final Invocation invocation) throws RpcException {
        //获取所有的服务列表
        List<Invoker<T>> invokers = directory.list(invocation);
        //获取merger配置
        String merger = getUrl().getMethodParameter(invocation.getMethodName(), Constants.MERGER_KEY);
        //如果没有指定merger配置,则选择一个可以利用的服务提供者进行调用(相当于AvailableClusterInvoker)
        if (ConfigUtils.isEmpty(merger)) { // If a method doesn't have a merger, only invoke one Group
            for (final Invoker<T> invoker : invokers) {
                if (invoker.isAvailable()) {
                    return invoker.invoke(invocation);
                }
            }
            //如果没有可利用的Invoker,则尝试使用第一个。这里应该加判断第一个是否有?
            return invokers.iterator().next().invoke(invocation);
        }
        //方法的返回类型
        Class<?> returnType;
        try {
            returnType = getInterface().getMethod(
                    invocation.getMethodName(), invocation.getParameterTypes()).getReturnType();
        } catch (NoSuchMethodException e) {
            returnType = null;
        }
    
        Map<String, Future<Result>> results = new HashMap<String, Future<Result>>();
        //异步调用所有的invoke
        for (final Invoker<T> invoker : invokers) {
            Future<Result> future = executor.submit(new Callable<Result>() {
                @Override
                public Result call() throws Exception {
                    return invoker.invoke(new RpcInvocation(invocation, invoker));
                }
            });
            results.put(invoker.getUrl().getServiceKey(), future);
        }
    
        Object result = null;
    
        List<Result> resultList = new ArrayList<Result>(results.size());
    
        int timeout = getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
        for (Map.Entry<String, Future<Result>> entry : results.entrySet()) {
            Future<Result> future = entry.getValue();
            try {
                Result r = future.get(timeout, TimeUnit.MILLISECONDS);
                //如果有异常则打印日志,没有异常则放到结果列表resultList中
                if (r.hasException()) {
                    log.error("Invoke " + getGroupDescFromServiceKey(entry.getKey()) +
                                    " failed: " + r.getException().getMessage(),
                            r.getException());
                } else {
                    resultList.add(r);
                }
            } catch (Exception e) {
                throw new RpcException("Failed to invoke service " + entry.getKey() + ": " + e.getMessage(), e);
            }
        }
        //如果结果列表为空,则返回一个空结果
        if (resultList.isEmpty()) {
            return new RpcResult((Object) null);
        } else if (resultList.size() == 1) {
            //如果只有一个结果,则直接返回
            return resultList.iterator().next();
        }
    
        if (returnType == void.class) {
            return new RpcResult((Object) null);
        }
        //根据方法来合并,将调用返回结果的指定方法进行合并
        //如果配置的是`merger=".addAll"`,则直接通过反射调用返回类型中的addAll方法合并结果集。
        //例如:返回类型是Set,则调用Set.addAll来合并结果
        if (merger.startsWith(".")) {
            //截取字符串,获取调用的方法名
            merger = merger.substring(1);
            Method method;
            try {
                //获取方法对象
                method = returnType.getMethod(merger, returnType);
            } catch (NoSuchMethodException e) {
                throw new RpcException("Can not merge result because missing method [ " + merger + " ] in class [ " +
                        returnType.getClass().getName() + " ]");
            }
            //非public方法设置暴力访问
            if (!Modifier.isPublic(method.getModifiers())) {
                method.setAccessible(true);
            }
            result = resultList.remove(0).getValue();
            try {
                if (method.getReturnType() != void.class
                        && method.getReturnType().isAssignableFrom(result.getClass())) {
                    // 方法返回类型匹配(不是void,而且与方法返回相同的类型),合并时,修改 result
                    for (Result r : resultList) {
                        result = method.invoke(result, r.getValue());
                    }
                } else {
                    // 方法返回类型不匹配,合并时,不修改 result
                    for (Result r : resultList) {
                        method.invoke(result, r.getValue());
                    }
                }
            } catch (Exception e) {
                throw new RpcException("Can not merge result: " + e.getMessage(), e);
            }
        } else {
            Merger resultMerger;
            //如果是默认的Merger(参数为true或default),则用MergerFactory获取默认的合并器
            //否则通过扩展加载器ExtensionLoader获取对应名称的合并器
            if (ConfigUtils.isDefault(merger)) {
                resultMerger = MergerFactory.getMerger(returnType);
            } else {
                resultMerger = ExtensionLoader.getExtensionLoader(Merger.class).getExtension(merger);
            }
            //如果找到合并器则合并,否则抛出异常
            if (resultMerger != null) {
                List<Object> rets = new ArrayList<Object>(resultList.size());
                for (Result r : resultList) {
                    rets.add(r.getValue());
                }
                result = resultMerger.merge(
                        rets.toArray((Object[]) Array.newInstance(returnType, 0)));
            } else {
                throw new RpcException("There is no merger to merge result.");
            }
        }
        return new RpcResult(result);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值