Dubbo 笔记(六) 集群容错和负载均衡

Dubbo 的集群容错和负载均衡

参考:敖丙:Dubbo 的集群容错和负载均衡

​ 线上的服务一般是要集群部署的,集群的话就要考虑消费者选择哪一个提供者调用调用失败了怎么办。这两点对应的就是负载均衡集群容错了。

invoker

​ 在 Dubboinvoker 其实就是一个具有调用功能的对象,在服务暴露端封装的就是真实的服务实现把真实的服务实现封装一下变成一个 invoker

​ 在服务发现端就是从注册中心得到服务提供者的配置信息,然后一条配置信息对应封装成一个 invoker,这个 invoker 就具备远程调用能力,当然要是走的是 injvm 协议那真实走的还是本地的调用。

​ 然后还有个 ClusterInvoker ,它也是个 invoker ,它封装了服务引入生成的 invoker 们,赋予其集群容错等能力,这个 invoker 就是暴露给消费者调用的 invoker。

​ 所以说 Dubbo 就是搞了个统一模型,将能调用的服务的对象都封装成 invoker

​ 这里主要讲的是服务消费者这边的事情,因为集群容错是消费者端实现的

服务目录

服务目录也就是 Directory,可以理解为服务的目录,但实际上它是一堆 invoker 的集合

服务的提供者都会集群部署,所有同样的服务一般都会有多个提供者,服务目录就负责管理这些服务提供者,当需要选择服务提供者时,就直接在服务目录中通过负载均衡算法挑选出一个即可。

​ 而服务提供者们也不是一成不变的,比如集群中增加了一台服务提供者,那么相应的服务目录就需要添加一个 invoker,下线了一台服务提供者,目录里面也需要删除对应的 invoker,修改了配置也一样得更新。

​ 所以这个服务目录其实还实现了监听注册中心的功能(指的是 RegistryDirectory )。

在这里插入图片描述

​ 使用一个抽象类来实现 Directory 接口,抽象类会实现一些公共方法,并且定义好逻辑,然后具体的实现由子类来完成,可以看到有两个子类,分别是 StaticDirectoryRegistryDirectory

RegistryDirectory

​ RegistryDirectory ,是一个动态目录。它实现了 NotifyListener 接口,所以它可以监听注册中心的变化,当服务中心的配置发生变化之后, RegistryDirectory 就可以收到变更通知,然后根据配置刷新其 Invoker 列表。

RegistryDirectory 一共有三大作用:

  1. 获取 invoker 列表RegistryDirectory 实现的父类抽象方法 doList,其目的就是得到 invoker 列表,而其内部的实现主要是做了层方法名的过滤,通过方法名找到对应的 invokers

    @Override
    public List<Invoker<T>> doList(Invocation invocation) {
         
        if (forbidden) {
         
            // 1. No service provider 2. Service providers are disabled
            throw new RpcException(...);
        }
        List<Invoker<T>> invokers = null;
        // 拿到 方法名 和 Invoker 的映射
        Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap;
        if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
         
            String methodName = RpcUtils.getMethodName(invocation);
            Object[] args = RpcUtils.getArguments(invocation);
            if (args != null && args.length > 0 && args[0] != null
                && (args[0] instanceof String || args[0].getClass().isEnum())) {
         
                invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter
            }
            if (invokers == null) {
         
                invokers = localMethodInvokerMap.get(methodName);
            }
            if (invokers == null) {
         
                invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
            }
            if (invokers == null) {
         
                Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
                if (iterator.hasNext()) {
         
                    invokers = iterator.next();
                }
            }
        }
        // 经过方法名过滤返回 invokers
        return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
    }
    
  2. 监听注册中心的变化:通过实现 NotifyListener 接口能感知到注册中心的数据变更,这其实是在服务引入的时候就订阅的。

    RegistryDirectory 定义了三种集合,分别是 invokerUrlsrouterUrlsconfiguratorUrls 分别处理相应的配置变化,然后对应转化成对象。

    @Override
    public synchronized void notify(List<URL> urls) {
         
        List<URL> invokerUrls = new ArrayList<URL>();
        List<URL> routerUrls = new ArrayList<URL>();
        List<URL> configuratorUrls = new ArrayList<URL>();
        for (URL url : urls) {
         
            // 按要求添加到上面三个 list 中
            ...
        }
        // configurators
        if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
         
            this.configurators = toConfigurators(configuratorUrls);
        }
        // routers
        if (routerUrls != null && !routerUrls.isEmpty()) {
         
            List<Router> routers = toRouters(routerUrls);
            if (routers != null) {
          // null - do nothing
                setRouters(routers);
            }
        }
        List<Configurator> localConfigurators = this.configurators; // local reference
        // merge override parameters
        this.overrideDirectoryUrl = directoryUrl;
        if (localConfigurators != null && !localConfigurators.isEmpty()) {
         
            for (Configurator configurator : localConfigurators) {
         
                this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
            }
        }
        // providers
        refreshInvoker(invokerUrls);
    }
    
  3. 刷新 invokers:其实就是根据监听变更的 invokerUrls 做一波操作,refreshInvoker(invokerUrls), 根据配置更新 invokers。

     private void refreshInvoker(List<URL> invokerUrls) {
         
         if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
             && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
         
             // 如果只有一个 invokerUrl 并且协议是 empty,则清楚所有的 invoker
             this.forbidden = true; // Forbid to access
             this.methodInvokerMap = null; // Set the method invoker map to null
             destroyAllInvokers(); // Close all invokers
         } else {
         
             // 根据 URL 生成 invoker map,然后方法名对应 invoker 的 map,再销毁无效的 invoker
             ...
             Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
             Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
             ...
             this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
             this.urlInvokerMap = newUrlInvokerMap;
             try {
         
                 destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
             } catch (Exception e) {
         
                 logger.warn("destroyUnusedInvokers error. ", e);
             }
         }
     }
    

    ​ 先根据 invokerUrls 数量和协议头是否是 empty,来决定是否禁用所有 invokers,如果不禁用,则将 url 转成 Invoker,得到 <url, Invoker> 的映射关系

    ​ 然后再进行转换,得到 <方法名, Invoker 列表> 映射关系,再将同一个组的 Invoker 进行合并,并将合并结果赋值给 methodInvokerMap,这个 methodInvokerMap 就是上面 doList 中使用的那个 Map。

    ​ 所以是在 refreshInvoker 的时候构造 methodInvokerMap,然后在调用的时候再读 methodInvokerMap,最后再销毁无用的 invoker。

StaticDirectory

StaticDirectory,这个是用在多注册中心的时候,它是一个静态目录,即固定的不会增减的,所有 Invoker 是通过构造器来传入。

​ 可以简单的理解成在单注册中心下我们配置的一条 reference 可能对应有多个 provider,然后生成多个 invoker,我们将它们存入 RegistryDirectory 中进行管理,为了便于调用再对外只暴露出一个 invoker 来封装内部的多 invoker 情况

​ 那多个注册中心就会有多个已经封装好了的 invoker ,这又面临了选择了,于是我们用 StaticDirectory 再来存入这些 invoker 进行管理,也再封装起来对外只暴露出一个 invoker 便于调用。

​ 之所以是静态的是因为多注册中心是写在配置里面的,不像服务可以动态变更。

​ StaticDirectory 的内部逻辑非常的简单,就是一个 list 存储了这些 invokers,然后实现父类的方法也就单纯的返回这个 list 不做任何操作。

@Override
protected List<Invoker<T>> doList(Invocation invocation) throws RpcException {
   
    return invokers;
}
服务路由

​ 服务路由其实就是路由规则,它规定了服务消费者可以调用哪些服务提供者,Dubbo 一共有三种路由分别是:条件路由 ConditionRouter脚本路由 ScriptRouter标签路由 TagRouter

条件路由

​ 条件路由是两个条件组成的,是这么个格式 [服务消费者匹配条件] => [服务提供者匹配条件],举个例子官网的例子就是 host = 10.20.153.10 => host = 10.20.153.11。该条规则表示 IP 为 10.20.153.10 的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。

​ 路由的配置一样是通过 RegistryDirectorynotify 更新和构造的,然后路由的调用在是刷新 invoker 的时候,具体是在调用 toMethodInvokers 的时候会进行服务级别的路由和方法级别的路由。

private Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) {
   
    ..
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Quantum_Wu

一起加油呀ヾ(◍°∇°◍)ノ゙

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值