资讯专栏INFORMATION COLUMN

dubbo源码解析——Directory

neu / 1398人阅读

摘要:首先来看一下接口的实现类他主要有两个实现类一个是一个是,本文主要解析。如果传入的列表为空,则意味着该规则仅是重写规则或路由规则,需要对其进行重新对比以决定是否重新引用。

首先来看一下directory接口的实现类,他主要有两个实现类,一个是StaticDirectory,一个是RegistryDirectory,本文主要解析RegistryDirectory。

StaticDirectory
StaticDirectory中的Static关键词来看,就知道,这个其实是不会动态变化的,从下图知道,他的Invoker是通过构造函数传入,StaticDirectory用得比较少,主要用在服务对多注册中心的引用

RegistryDirectory
首先看下它的结构:

这个NotifyListener中的notify方法就是注册中心的回调,也就是它之所以能根据注册中心动态变化的根源所在.

上文中,Directory的doList方法,这是一个抽象方法,通过调用子类RegistryDirectory的doList方法,从Directory中选择出invoker列表。

@Override
    public List> doList(Invocation invocation) {
        if (forbidden) {
            // 如果forbidden为true,则没有服务提供者  或者  服务提供者不可用
            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> invokers = null;
        Map>> 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]);
            }
            if (invokers == null) {
                // 仍然为null,使用方法名称获取
                invokers = localMethodInvokerMap.get(methodName);
            }
            if (invokers == null) {
                // 仍让为null,使用 * 随机取一个invoker来实现调用
                invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
            }
            if (invokers == null) {
                // 仍然为null,使用迭代器获取一个invoker
                Iterator>> iterator = localMethodInvokerMap.values().iterator();
                if (iterator.hasNext()) {
                    invokers = iterator.next();
                }
            }
        }
        return invokers == null ? new ArrayList>(0) : invokers;
    }

从中可以看出,Directory获取invoker是从methodInvokerMap中获取的。invoker什么时候写入到Directory的methodInvokerMap里面呢?就是在回调方法notify的时候操作的。

@Override
    public synchronized void notify(List urls) {
        // 声明invoker的URL引用数组
        List invokerUrls = new ArrayList();

        // 声明router的URL引用数组
        List routerUrls = new ArrayList();

        // 声明configurator(配置器)的URL引用数组
        List configuratorUrls = new ArrayList();

        for (URL url : urls) {
            // 获取协议名称
            String protocol = url.getProtocol();
            // 获取分类
            String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            
            if (Constants.ROUTERS_CATEGORY.equals(category)
                    || Constants.ROUTE_PROTOCOL.equals(protocol)) {
                // 如果是路由分类 或者  路由协议
                routerUrls.add(url);
            } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
                    || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
                // 如果是配置器分类 或者 重写协议
                configuratorUrls.add(url);
            } 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());
            }
        }
        // 通过url获取configurators
        if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
            this.configurators = toConfigurators(configuratorUrls);
        }
        // 通过url获取routers
        if (routerUrls != null && !routerUrls.isEmpty()) {
            List routers = toRouters(routerUrls);
            if (routers != null) { // null - do nothing
                setRouters(routers);
            }
        }
        List localConfigurators = this.configurators;
        // 合并override参数
        this.overrideDirectoryUrl = directoryUrl;
        if (localConfigurators != null && !localConfigurators.isEmpty()) {
            for (Configurator configurator : localConfigurators) {
                this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
            }
        }
        // 刷新providers
        refreshInvoker(invokerUrls);
    }

/**
     * 把invokerUrl列表 转化成 invoker Map。转换规则如下
     * 1.如果URL已经转换为invoker,则不再重新引用它,并且直接从缓存中获得它,并且注意URL中的任何参数更改都将被重新引用。
     * 2.如果传入的invoker列表不是空的,则意味着它是最新的调用列表。
     * 3.如果传入的invokerUrl列表为空,则意味着该规则仅是重写规则或路由规则,需要对其进行重新对比以决定是否重新引用。
     * @param invokerUrls this parameter can"t be null
     */
    private void refreshInvoker(List invokerUrls) {
        if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
                && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            // 只有一个invoker url,并且协议是空协议

            // 设置禁止访问
            this.forbidden = true;
            // 设置methodInvokerMap为null
            this.methodInvokerMap = null;
            // 关闭所有invoker
            destroyAllInvokers();

        } else {
            // 设置允许访问
            this.forbidden = false;

            Map> oldUrlInvokerMap = this.urlInvokerMap;

            if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
                // 如果传入的invoker url是空的,从缓存中添加
                invokerUrls.addAll(this.cachedInvokerUrls);
            } else {
                // 缓存invoker url,便于比较
                this.cachedInvokerUrls = new HashSet();
                this.cachedInvokerUrls.addAll(invokerUrls);
            }
            if (invokerUrls.isEmpty()) {
                return;
            }
            // 把invoker url列表 转换成 key为url,value为invoker的Map
            Map> newUrlInvokerMap = toInvokers(invokerUrls);
            // 把invoker url列表 转换成 key为method,value为invoker的Map
            Map>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap);

            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;
            }
            // 如果有多个分组,就合并methodInvokerMap
            this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
            this.urlInvokerMap = newUrlInvokerMap;
            try {
                // 关闭没有使用invoker
                destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap);
            } catch (Exception e) {
                logger.warn("destroyUnusedInvokers error. ", e);
            }
        }
    }

也就是注册中心有变化,则更新methodInvokerMap和urlInvokerMap的值(这个后面讲服务引用原理的时候会再提一下),这就是官网提到的它的值可能是动态变化的,比如注册中心推送变更的原因所在

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72099.html

相关文章

  • dubbo源码解析——消费过程

    摘要:上一篇源码解析概要篇中我们了解到中的一些概念及消费端总体调用过程。由于在生成代理实例的时候,在构造函数中赋值了,因此可以只用该进行方法的调用。 上一篇 dubbo源码解析——概要篇中我们了解到dubbo中的一些概念及消费端总体调用过程。本文中,将进入消费端源码解析(具体逻辑会放到代码的注释中)。本文先是对消费过程的总体代码逻辑理一遍,个别需要细讲的点,后面会专门的文章进行解析。...

    darkbug 评论0 收藏0
  • dubbo源码解析(四)注册中心——dubbo

    摘要:一该类继承了类,该类里面封装了一个重连机制,而注册中心核心的功能注册订阅取消注册取消订阅,查询注册列表都是调用了我上一篇文章源码解析三注册中心开篇中讲到的实现方法,毕竟这种实现注册中心的方式是默认的方式,不过推荐使用,这个后续讲解。 注册中心——dubbo 目标:解释以为dubbo实现的注册中心原理,解读duubo-registry-default源码 dubbo内置的注册中心实现方式...

    andot 评论0 收藏0
  • dubbo源码解析——概要篇

    摘要:服务提供者代码上面这个类会被封装成为一个实例,并新生成一个实例。这样当网络通讯层收到一个请求后,会找到对应的实例,并调用它所对应的实例,从而真正调用了服务提供者的代码。 这次源码解析借鉴《肥朝》前辈的dubbo源码解析,进行源码学习。总结起来就是先总体,后局部.也就是先把需要注意的概念先抛出来,把整体架构图先画出来.让读者拿着地图跟着我的脚步,并且每一步我都提醒,现在我们在哪,我们下一...

    Meathill 评论0 收藏0
  • dubbo源码解析(四十五)服务引用过程

    摘要:服务引用过程目标从源码的角度分析服务引用过程。并保留服务提供者的部分配置,比如版本,,时间戳等最后将合并后的配置设置为查询字符串中。的可以参考源码解析二十三远程调用的一的源码分析。 dubbo服务引用过程 目标:从源码的角度分析服务引用过程。 前言 前面服务暴露过程的文章讲解到,服务引用有两种方式,一种就是直连,也就是直接指定服务的地址来进行引用,这种方式更多的时候被用来做服务测试,不...

    xiaowugui666 评论0 收藏0
  • dubbo之SPI

    摘要:简介全称为,是一种服务发现机制。的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。不过,并未使用原生的机制,而是对其进行了增强,使其能够更好的满足需求。并未使用,而是重新实现了一套功能更强的机制。 1、SPI简介 SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中...

    UnixAgain 评论0 收藏0

发表评论

0条评论

neu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<