jannal(无名小宝)

没有失败,只有缓慢的成功

导航

Dubbo之Cluster目录服务

dubbo版本

  1. dubbo版本2.6.7

目录服务

  1. 目录服务(Directory service):是一个储存、组织和提供信息访问服务的软件系统。一个目录是指一组名字和值的映射。它允许根据一个给出的名字来查找对应的值,与词典相似。像词典中每一个词也许会有多个词义,在一个目录中,一个名字也许会与多个不同的信息相关联。

  2. 目录一般只提供范围非常小的节点类型和数值类型,也可能对任意的或可扩展的一组类型提供支持。比如在一个电话目录中,节点就是姓名而数值项就是电话号码

  3. 在Dubbo中目录服务存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息。Dubbo中目录服务其实就是Invoker对象的集合,这个集合中的元素会随注册中心的变化而进行动态调整。

  4. Directory既提供静态的Invoker列表(用户设置),也提供了动态的Invoker列表(注册中心动态变化)

    Directory

AbstractDirectory

  1. AbstractDirectory是模板类,封装了通用的流程

    • 检查Directory是否已经销毁
    • 子类实现获取Invoker列表
    • 获取路由Router列表,过滤Invoker列表
    @Override
    public List<Invoker<T>> list(Invocation invocation) throws RpcException {
        if (destroyed) {
            throw new RpcException("Directory already destroyed .url: " + getUrl());
        }
        //子类实现获取Invoker列表
        List<Invoker<T>> invokers = doList(invocation);
        // 获取路由 Router 列表
        List<Router> localRouters = this.routers; // local reference
        if (localRouters != null && !localRouters.isEmpty()) {
            for (Router router : localRouters) {
                try {
                    //runtime参数决定了是否在每次调用服务时都执行路由规则。
                    //如果runtime为true,那么每次调用服务前,都需要进行服务路由。
                    if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
                        invokers = router.route(invokers, getConsumerUrl(), invocation);
                    }
                } catch (Throwable t) {
                    logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
                }
            }
        }
        return invokers;
    }
    

StaticDirectory

  1. StaticDirectory#doList逻辑非常简单,不做任何处理,直接返回Invoker列表

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

RegistryDirectory

  1. RegistryDirectory 可根据配置变更信息刷新 Invoker 列表。RegistryDirectory的重要逻辑

    • 列举Invoker列表
    • 接收服务配置变更的逻辑
    • 刷新 Invoker 列表
  2. RegistryDirectory是一种动态目录服务,实现了NotifyListener,当注册中心服务配置发生变化后,RegistryDirectory 可收到与当前服务相关的变化。收到变更通知后,RegistryDirectory 可根据配置变更信息刷新 Invoker 列表

  3. 列举Invoker列表:doList本质就是对methodInvokerMap的读取操作

    @Override
    public List<Invoker<T>> doList(Invocation invocation) {
        // 服务提供者关闭或禁用了服务,此时抛出 No provider 异常
        if (forbidden) {
            // 1. No service provider 2. Service providers are disabled
            throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
                    "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost()
                            + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist).");
        }
        List<Invoker<T>> invokers = null;
        // 获取 Invoker 本地缓存,这里其实赋值本地变量是没有必要的
        Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference
        if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
            //获取方法名和第一个参数
            String methodName = RpcUtils.getMethodName(invocation);
            Object[] args = RpcUtils.getArguments(invocation);
            // 检测参数列表的第一个参数是否为 String 或 enum 类型
            if (args != null && args.length > 0 && args[0] != null
                    && (args[0] instanceof String || args[0].getClass().isEnum())) {
                // 通过 方法名 + 第一个参数名称 查询 Invoker 列表,具体的使用场景不太清楚
                invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter
            }
            if (invokers == null) {
                // 通过方法名获取 Invoker 列表
                invokers = localMethodInvokerMap.get(methodName);
            }
            if (invokers == null) {
                // 通过星号 * 获取 Invoker 列表(泛化调用)
                invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
            }
            // 冗余逻辑,pull request #2861 移除了下面的 if 分支代码
            // https://github.com/apache/dubbo/pull/2861
            //provider没有方法,但是consumer依然能获取到invoker
            if (invokers == null) {
                Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
                if (iterator.hasNext()) {
                    invokers = iterator.next();
                }
            }
        }
        return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
    }
    

订阅与动态更新

  1. 订阅与动态更新主要涉及subscribe、notify、refreshInvoker三个方法

  2. RegistryDirectory#subscribe 就是调用注册中心的subscribe方法,RegistryDirectory实现了NotifyListener,所以传入的就是自己

    public void subscribe(URL url) {
        //设置consumer url
        setConsumerUrl(url);
        //调用注册中心订阅消息消息消费者URL,listener就是自己
        registry.subscribe(url, this);
    }
    
  3. NotifyListener是一个通知接口,当收到服务变更通知时触发。

    public interface NotifyListener {
    
        /**
         * 当收到服务变更通知时触发。
         * 通知需处理契约:
         * 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。<br>
         * 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。<br>
         * 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。<br>
         * 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。<br>
         * 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。<br>
         */
        void notify(List<URL> urls);
    
    }
    
  4. RegistryDirectory#notify是在注册中心providers、configurators、routers目录下的节点发生变化后,通知RegistryDirectory,从而实现动态发现机制

     @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>();
         //根据通知的URL的前缀,分别添加到:
         // invokerUrls(提供者url)、routerUrls(路由信息)、configuratorUrls (配置url)。
         for (URL url : urls) {
             //获取URL协议字段,例如condition://、route://、script://、override://等
             String protocol = url.getProtocol();
             //获取url的category,providers、configurators、routers
             String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
             //routers或者route
             if (Constants.ROUTERS_CATEGORY.equals(category)
                     || Constants.ROUTE_PROTOCOL.equals(protocol)) {
                 routerUrls.add(url);
              //configurators和override
             } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
                     || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
                 configuratorUrls.add(url);
                 //providers
             } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
                 invokerUrls.add(url);
             } else {
                 logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
             }
         }
         // 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);
     }
    
  5. refreshInvoker 方法首先会根据入参 invokerUrls 的数量和协议头判断是否禁用所有的服务,如果禁用,则将 forbidden 设为 true,并销毁所有的 Invoker。若不禁用,则将 url 转成 Invoker,得到 的映射关系。然后进一步进行转换,得到 映射关系。之后进行多组 Invoker 合并操作,并将合并结果赋值给 methodInvokerMap。methodInvokerMap 变量在 doList 方法中会被用到,doList 会对该变量进行读操作,在这里是写操作。当新的 Invoker 列表生成后,还要一个重要的工作要做,就是销毁无用的 Invoker,避免服务消费者调用已下线的服务的服务。

    rivate void refreshInvoker(List<URL> invokerUrls) {
        // invokerUrls 仅有一个元素,且 url 协议头为 empty,此时表示禁用所有服务
        if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
                && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            this.forbidden = true; // Forbid to access
            this.methodInvokerMap = null; // Set the method invoker map to null
            // 销毁所有 Invoker
            destroyAllInvokers(); // Close all invokers
        } else {
            this.forbidden = false; // Allow to access
            Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
            if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
                // 添加缓存 url 到 invokerUrls 中
                invokerUrls.addAll(this.cachedInvokerUrls);
            } else {
                this.cachedInvokerUrls = new HashSet<URL>();
                // 缓存 invokerUrls
                this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
            }
            if (invokerUrls.isEmpty()) {
                return;
            }
            // 将 url 转成 Invoker
            Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
            // 将 newUrlInvokerMap 转成方法名到 Invoker 列表的映射
            Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
            // state change
            // If the calculation is wrong, it is not processed.
            // 转换出错,直接打印异常,并返回
            if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
                logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
                return;
            }
            // 合并多个组的 Invoker
            this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
            this.urlInvokerMap = newUrlInvokerMap;
            try {
                // 销毁无用 Invoker
                destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
            } catch (Exception e) {
                logger.warn("destroyUnusedInvokers error. ", e);
            }
        }
    }
    

posted on 2021-12-26 09:31  jannal  阅读(43)  评论(0编辑  收藏  举报