Dubbo的Cluster是在哪里包装了Invoker的

在这里插入图片描述

这张图很好的说明了Dubbo中Cluster的作用,但是Cluster是如何起作用的,如何包装Inovker的呢? 我会在下面的文章中说明。

Directory

维护服务提供者列表(List),并且实时监控服务提供者的状态,当服务提供者下线后,需要把提供其移除。 并且这类还会保存有本地缓存, 当注册中心挂了后就会使用本地缓存的信息。

Router

路由, 比如我们需要同机房的调用,或者同地域的调用,可以实现这个接口来满足需求,这个接口的逻辑是把Directory中的所有服务者按照要求拿出来给后面的LoadBalance来使用。

从图中看到好像有两种方式可以 脚本和编码的方式来实现不同的路由。 这部分我还没有了解, 后面会深入了解的。

LoadBalance

负载均衡的实现, 这个我比较熟悉,这个主要是把Router提供的服务者 按照规则挑选一个来调用。

Cluster

把上面的所有接口封装起来的接口, 并且它会把所有的服务提供者封装成一个逻辑的Invoker 给上层使用, 他会屏蔽路由负载均衡 和失败策略的逻辑,对上层来说就是简单的调用。

它会根据不同的失败策略有不同的实现, 默认是的FailOver 实现, 就是失败了后会换个服务提供者调用,默认会换两个调用者重试。

Cluster 是通过SPI加载的。

SPI的配置文件地址在dubbo-cluster模块的resources/META-INF.dubbo.internal目录下
在这里插入图片描述
而Cluster是给消费者使用的,所以使用的地方自然是org.apache.dubbo.config.ReferenceConfig类了。

ReferenceConfig 类

看下ReferenceConfig中类的源码
在这里插入图片描述
最重要的三大类都是通过SPI加载到ReferenceConfig 类中。

ReferenceConfig的调用流程

ReferenceConfig类是一个FactoryBean类, 它最终是把get方法的返回值放入容器的, 就是在get方法中会调用一次init方法,在init方法中会调用createProxy类生成代理类
在这里插入图片描述

createProxy创建代理类

生成代理类的方法 org.apache.dubbo.config.ReferenceConfig#createProxy

   private T createProxy(Map<String, String> map) {
        if (shouldJvmRefer(map)) {
           ...........本地服务提供者的逻辑...忽略掉.................
        } else {
          
            ..... 对于Url的校验处理,忽略.................
            }

            if (urls.size() == 1) {
            //	只有一个服务提供者, 直接使用DubboProtocol生成对应的Invoker。 集群就没有意义了,所以直接返回这个Invoker
                invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
            } else {
            //对于多个URL(服务提供者), 逐个遍历
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                for (URL url : urls) {
                	//逐个生成Invoker并加入到List中
                    invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
                    if (UrlUtils.isRegistry(url)) {
                        registryURL = url; // use last registry url
                    }
                }
                if (registryURL != null) { // registry url is available
                    // for multi-subscription scenario, use 'zone-aware' policy by default
                    URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
                    // The invoker wrap relation would be like: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
                    invoker = CLUSTER.join(new StaticDirectory(u, invokers));
                } else { // not a registry url, must be direct invoke.
                	// 调用CLUSTER的spi方法, 把所有的Invoker都封装到Directory中, 并返回一个包装好的Invoker
                    invoker = CLUSTER.join(new StaticDirectory(invokers));
                }
            }
        }

        if (shouldCheck() && !invoker.isAvailable()) {
            invoker.destroy();
            throw new IllegalStateException("Failed to check the status of the service "
                    + interfaceName
                    + ". No provider available for the service "
                    + (group == null ? "" : group + "/")
                    + interfaceName +
                    (version == null ? "" : ":" + version)
                    + " from the url "
                    + invoker.getUrl()
                    + " to the consumer "
                    + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
        }
        if (logger.isInfoEnabled()) {
            logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
        }
        /**
         * @since 2.7.0
         * ServiceData Store
         */
        String metadata = map.get(METADATA_KEY);
        WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
        if (metadataService != null) {
            URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
            metadataService.publishServiceDefinition(consumerURL);
        }
        // create service proxy  
        //根据返回包装好的Invoker, 生成代理类,proxyFactory是使用字节码的方式生成代理类的
        return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
    }

整个createProxy的逻辑如下:

  • 先校验URL, 并完善处理好URL的内容,这部分的逻辑不重要, 在上面的源码中也忽略掉了
  • 遍历每个URL, 每个URL都调用DubboProtocol#refer来生成一个Invoker , 其中每个URL代表一个服务者
  • 把Invoker的的list集合传入Cluster的join方法中, 并返回一个Invoker。 Cluster的逻辑在后文中说明。
  • 把Cluster返回的Invoker传入ProxyFactory#getProxy来生成放入容器中的代理类的Bean , ProxyFactory内部是使用字节码的方式生成代理类的。
Cluster#join方法

在这里插入图片描述
它的默认实现类是FailoverCluster

/**
 * {@link FailoverClusterInvoker}
 *
 */
public class FailoverCluster extends AbstractCluster {

    public final static String NAME = "failover";

    @Override
    public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<>(directory);
    }
}

返回的实际上是FailoverClusterInvoker类, 看下FailoverClusterInvoker类;
直接看其核心方法

 /**
     *
     * @param invocation  方法调用参数等信息的封装类
     * @param invokers  Router挑选过一遍的服务提供者
     * @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);
        String methodName = RpcUtils.getMethodName(invocation);
        //获取配置的重试次数。 默认是2+1次
        int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, 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 " + methodName
                            + " 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.getCode(), "Failed to invoke the method "
                + methodName + " 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.getMessage(), le.getCause() != null ? le.getCause() : le);
    }

在这个方法中会处理配置的重试次数,会循环设置的次数, 如何循环次数用完了还没有成功,就会抛出异常,默认是2+1次。

具体的选择调用者的逻辑又会跑到父类中, 在父类中会使用会使用loadBalance类的select方法来确定具体的invoker。

总结

Cluster是Dubbo的集群负载处理接口, 默认实现是FailoverCluster。 它的作用场景是在ReferenceConfig中, 它会返回 FailoverClusterInvoker来代替所有服务提供者Invoker,并且Inovker的选择逻辑也是封装在FailoverClusterInvoker中的, Router和LoadBalance的逻辑都是封装在这个类中, 最后会使用ProxyFactory来根据这个FailoverClusterInvoker创建出代理类放入容器中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值