Dubbo笔记衍生篇⑩:2.7 版本的动态配置监听

Dubbo笔记衍生篇⑩:2.7 版本的动态配置监听,第1张

Dubbo笔记衍生篇⑩:2.7 版本的动态配置监听

文章目录
  • 一、前言
    • 1. 前提
  • 二、提供者
    • 1. RegistryProtocol#overrideUrlWithConfig
      • 1.1 ServiceConfigurationListener
        • 1.1.1 DynamicConfiguration#getDynamicConfiguration
        • 1.1.2 DynamicConfiguration#addListener
        • 1.1.3 AbstractConfiguratorListener#process
      • 1.2 ProviderConfigurationListener
    • 2. RegistryService#subscribe
    • 3. 总结
  • 三、消费者
    • 1. ConsumerConfigurationListener & ReferenceConfigurationListener
      • 1.1 RegistryDirectory#mergeUrl

一、前言

本系列为个人Dubbo学习笔记衍生篇,是正文篇之外的衍生内容,内容来源于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章。仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。


1. 前提

Dubbo 在 2.7 版本之后提供了新的监听方式。本文来分析 Dubbo 2.6 和 Dubbo 2.7 版本之间对于配置更新的差异。

首先我们需要知道 Dubbo 2.7提供了 应用级别配置 和 服务级别配置

  • 应用级别配置:配置会作用于指定的应用名的应用。Dubbo 2.7 及以上新增的配置方式,会在配置中心上创建节点。
  • 服务级别配置:配置只会作用于指定的接口服务。

综述,如下图(下图中的zk充当注册中心和配置中心,因此会创建下面三个节点):

  • 节点 ① :Dubbo 2.6 版本使用的配置节点。当我们使用 dubbo-admin 进行配置时会创建该节点由于Dubbo 2.6 版本不存在配置中心和元数据中心,所以节点会在注册中心上创建,路径规则为 /dubbo/{服务接口全路径}/configurations
  • 节点 ② :Dubbo 2.7 版本使用的配置节点,应用级别配置,该节点创建在配置中心上。路径规则为 /dubbo/config/{应用名}/configurations
  • 节点 ③ :Dubbo 2.7 版本使用的配置节点,服务级别配置,该节点创建在配置中心上。路径规则为 /dubbo/config/{分组}/{服务接口全路径}:{版本号}/configurations。该节点的内容等价于节点 ①的内容(实际节点内容由于生成规则不同有所不同,但其所包含的配置内容是等价的)。

本文使用的 dubbo-admin-0.2.0 来进行动态配置,该版本会兼容 2.6 和 2.7 版本,因此如果进行服务级别动态配置,则上图中的 ① 和 ③ 两个配置节点都会创建。如果进行应用级别动态配置,则会创建 ② 的配置节点,因为 Dubbo2.6版本不支持 应用级别配置。

关于上述内容,在 Dubbo笔记 ㉒ :配置中心 中有较为完整的描述,如有需要可阅读。


基于上述前提,我们来看下面的内容。

二、提供者

我们知道提供者在服务暴露时的核心方法是 RegistryProtocol#export,这里我们忽略其他代码,仅看如下代码:

    @Override
    public  Exporter export(final Invoker originInvoker) throws RpcException {

    	.... // 获取服务接口 URL, 获取注册中心 URL
    	
    	// 获取订阅覆盖 URL
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        // 生成 对 overrideSubscribeUrl  的监听器
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
		// 1. 从配置中心加载最新的配置并监听配置
        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        
      	....  // 将服务注册到注册中心
      	
        // Deprecated! Subscribe to override rules in 2.6.x or before.
        // 2. 订阅并监听配置
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        
		.... // 收尾工作
		
        return new DestroyableExporter<>(exporter);
    }

这里我们主要关注两个方法 :

  1. RegistryProtocol#overrideUrlWithConfig :Dubbo 2.7 及以上提供的新的订阅方式。通过 ProviderConfigurationListener 和 ServiceConfigurationListener 监听器来监听动态配置,处理应用级别和服务级别的配置。
  2. RegistryService#subscribe :Dubbo 2.7 以下版本使用的订阅方式,可以用于处理服务级别的配置。

下面我们详细来看这两个方法的具体实现:


1. RegistryProtocol#overrideUrlWithConfig

该函数是 Dubbo2.7 版本提供的,Dubbo 2.6 版本所有数据都存在注册中心上,Dubbo 2.7版本分成了注册中心,配置中心,和元数据中心。

	private final ProviderConfigurationListener providerConfigurationListener = new ProviderConfigurationListener();
	
    private final Map serviceConfigurationListeners = new ConcurrentHashMap<>();

    private URL overrideUrlWithConfig(URL providerUrl, OverrideListener listener) {
    	// 获取配置中心应用级别配置并覆盖本地配置
        providerUrl = providerConfigurationListener.overrideUrl(providerUrl);
        // 创建服务级别配置监听器
        ServiceConfigurationListener serviceConfigurationListener = new ServiceConfigurationListener(providerUrl, listener);
        serviceConfigurationListeners.put(providerUrl.getServiceKey(), serviceConfigurationListener);
        // 获取配置中心服务级别配置并覆盖本地配置
        return serviceConfigurationListener.overrideUrl(providerUrl);
    }

这里我们的关键点在于两个监听器 :

  • ProviderConfigurationListener : 应用级别监听器,因为一个程序只能有一个应用名,所以这里之需要一个 ProviderConfigurationListener 实例即可。
  • ServiceConfigurationListener :服务级别监听器。因为一个程序可以提供多个 Dubbo 接口服务,所以这使用 serviceConfigurationListeners 来保存不同接口的监听器。

我们这里先来看 ServiceConfigurationListener 之后再来看 ProviderConfigurationListener 。

1.1 ServiceConfigurationListener

ServiceConfigurationListener 用于 Dubbo 2.7 及以后 来处理 服务级别的配置,在 ServiceConfigurationListener 的构造函数中会通过 this.initWith(providerUrl.getEncodedServiceKey() + CONFIGURATORS_SUFFIX) 来监听当前服务的配置。其实现如下:

    private class ServiceConfigurationListener extends AbstractConfiguratorListener {
        private URL providerUrl;
        private OverrideListener notifyListener;

        public ServiceConfigurationListener(URL providerUrl, OverrideListener notifyListener) {
            this.providerUrl = providerUrl;
            this.notifyListener = notifyListener;
            // 这里需要注意,在ServiceConfigurationListener  创建时会通过该方法订阅配置中心的配置。
            this.initWith(providerUrl.getEncodedServiceKey() + CONFIGURATORS_SUFFIX);
        }

        private  URL overrideUrl(URL providerUrl) {
            return RegistryProtocol.getConfigedInvokerUrl(configurators, providerUrl);
        }

        @Override
        protected void notifyOverrides() {
            notifyListener.doOverrideIfNecessary();
        }
    }

这里的关键在于 ServiceConfigurationListener#initWith ,其实现在其父类中完成,如下:

	// AbstractConfiguratorListener#initWith
    protected final void initWith(String key) {
    	// 1. 获取配置中心
        DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();
        // 2. 添加监听器
        dynamicConfiguration.addListener(key, this);
        // 根据 key 获取配置中心上的配置
        String rawConfig = dynamicConfiguration.getConfig(key);
        if (!StringUtils.isEmpty(rawConfig)) {
        	// 3. 如果配置不为空,则会解析配置并更新本地配置
            process(new ConfigChangeEvent(key, rawConfig));
        }
    }

下面我们按照注释的顺序一一来看:

1.1.1 DynamicConfiguration#getDynamicConfiguration

DynamicConfiguration#getDynamicConfiguration 实现如下:

    static DynamicConfiguration getDynamicConfiguration() {
    	// 获取动态配置中心,这里即获取的配置中心的对象
        Optional optional = Environment.getInstance().getDynamicConfiguration();
        // 如果没有配置中心,则获取默认的 DynamicConfiguration 
        return (DynamicConfiguration) optional.orElseGet(() -> getExtensionLoader(DynamicConfigurationFactory.class)
                .getDefaultExtension()
                .getDynamicConfiguration(null));
    }

我们这里以 zk 为配置中心,所以获取的是 ZookeeperDynamicConfiguration 实例。ZookeeperDynamicConfiguration 会在构造函数中建立与 ZK 的连接并获取节点内容缓存到本地。

1.1.2 DynamicConfiguration#addListener

DynamicConfiguration#addListener 是实现监听服务级别配置的的核心方法,这里调用的是 DynamicConfiguration 接口的默认方法如下

	// org.apache.dubbo.configcenter.DynamicConfiguration#addListener(java.lang.String, org.apache.dubbo.configcenter.ConfigurationListener)
    default void addListener(String key, ConfigurationListener listener) {
    	// 分组指定为 dubbo 
        addListener(key, DEFAULT_GROUP, listener);
    }

随后通过如下调用顺序:

ZookeeperDynamicConfiguration#addListener -> CacheListener#addListener

CacheListener#addListener 实现如下:

	private Map> keyListeners = new ConcurrentHashMap<>();

    public void addListener(String key, ConfigurationListener configurationListener) {
    	// 将 key 和 configurationListener 保存到了 keyListeners 中。
        Set listeners = this.keyListeners.computeIfAbsent(key, k -> new CopyOnWriteArraySet<>());
        listeners.add(configurationListener);
    }

这里可以看到,这里会将当前接口服务的key 和监听器保存到 CacheListener#keyListeners 中。而当我们进行动态配置时会修改配置中心的节点,而当 zk 节点变化时会触发 CacheListener#childEvent 方法,其实现如下:

    @Override
    public void childEvent(Curatorframework aClient, TreeCacheEvent event) throws Exception {

        TreeCacheEvent.Type type = event.getType();
        ChildData data = event.getData();
        if (type == TreeCacheEvent.Type.INITIALIZED) {
            initializedLatch.countDown();
            return;
        }

        // TODO, ignore other event types
        if (data == null) {
            return;
        }

        // TODO We limit the notification of config changes to a specific path level, for example
        //  /dubbo/config/service/configurators, other config changes not in this level will not get notified,
        //  say /dubbo/config/dubbo.properties
        if (data.getPath().split("/").length >= 5) {
            byte[] value = data.getData();
            // 根据节点path 转换为 key
            String key = pathToKey(data.getPath());
            ConfigChangeType changeType;
            switch (type) {
                case NODE_ADDED:
                    changeType = ConfigChangeType.ADDED;
                    break;
                case NODE_REMOVED:
                    changeType = ConfigChangeType.DELETeD;
                    break;
                case NODE_UPDATED:
                    changeType = ConfigChangeType.MODIFIED;
                    break;
                default:
                    return;
            }

            ConfigChangeEvent configChangeEvent = new ConfigChangeEvent(key, new String(value, StandardCharsets.UTF_8), changeType);
            // 根据转换的key 来获取对应的监听器,该监听器在 
            Set listeners = keyListeners.get(key);
            if (CollectionUtils.isNotEmpty(listeners)) {
                listeners.forEach(listener -> listener.process(configChangeEvent));
            }
        }
    }

那么这里我们就可以得知:当配置中心上有节点变动时会触发 CacheListener#childEvent 方法,而在 CacheListener#childEvent 方法中会通过 pathToKey(data.getPath()); 将path 转为 key,随后依据 key 从 keyListeners 找到对应节点的监听器,并执行 listener.process(configChangeEvent) 来触发监听器 *** 作。


上述理论上如此,但需要注意的是:

当我们使用zk 作为配置中心时 Dubbo的服务级别配置是通过 RegistryService#subscribe 完成更新,而不是 通过 ServiceConfigurationListener 完成,因为当配置节点更新时并不会触发监听器 *** 作, 原因在于keyListeners 中的key 和 pathToKey(data.getPath()); 转化的key 并不一致。

并且

  1. 该种情况仅针对于 服务级别配置,应用级别配置并不会出现该种情况。
  2. 个人测试,如果使用 Nacos 来作为配置中心则可以执行 ServiceConfigurationListener 的监听器逻辑,所以推测可能是 Dubbo对 zk 作为配置中心时的一个缺陷 ?

上述问题的原因如下 :

在 Dubbo 2.7.0 的版本下,ServiceConfigurationListener 在初始化时会传入监听器的 key,而这个key是通过 providerUrl.getEncodedServiceKey() + CONFIGURATORS_SUFFIX 生成。

其中 URL#getEncodedServiceKey 实现如下,可以看到仅仅是将 serviceKey 的第一个 / 替换为了 *

    public String getEncodedServiceKey() {
        String serviceKey = this.getServiceKey();
        serviceKey = serviceKey.replaceFirst("/", "*");
        return serviceKey;
    }

所以实际上 CacheListener#keyListeners 中监听的 key 为dubbo*com.kingfish.service.impl.SimpleDemoService:1.0.0.configurators。也即是说对于 com.kingfish.service.impl.SimpleDemoService 这个服务接口,我们对应的监听器的key 为 dubbo*com.kingfish.service.impl.SimpleDemoService:1.0.0.configurators 。

如下图:


而当我们修改动态配置时,即zk节点发生变化,会触发 CacheListener#childEvent 方法。其中会根据节点路径转换为对应的key,如下:

1. 修改的节点路径如下:
data.getPath() = /dubbo/config/dubbo/com.kingfish.service.impl.SimpleDemoService:1.0.0/configurators
2. key 的生成规则如下:
key = pathToKey(data.getPath()) 
	= dubbo.com.kingfish.service.impl.SimpleDemoService:1.0.0.configurators

也即是说,当我们修改 com.kingfish.service.impl.SimpleDemoService 这个服务接口的动态配置时,Dubbo 根据 zk 节点路径转化的Key 为 dubbo.com.kingfish.service.impl.SimpleDemoService:1.0.0.configurators。而我们实际的key为 dubbo*com.kingfish.service.impl.SimpleDemoService:1.0.0.configurators 。从而导致我们无法通过path转化的key找到对应服务的监听器。

如下图,由于变更节点转化的key 与 keyListeners中的key 不匹配导致 无法根据变更节点找到对应的监听器来进行处理:

1.1.3 AbstractConfiguratorListener#process
AbstractConfiguratorListener#process 
-> RegistryProtocol.ServiceConfigurationListener#notifyOverrides 
-> RegistryProtocol.OverrideListener#doOverrideIfNecessary

在 RegistryProtocol.OverrideListener#doOverrideIfNecessary 中 完成了本地的配置更新,如下:

        public synchronized void doOverrideIfNecessary() {
            final Invoker invoker;
            if (originInvoker instanceof InvokerDelegate) {
                invoker = ((InvokerDelegate) originInvoker).getInvoker();
            } else {
                invoker = originInvoker;
            }
            //The origin invoker
            // 获取 原始 URL
            URL originUrl = RegistryProtocol.this.getProviderUrl(invoker);
            String key = getCacheKey(originInvoker);
            ExporterChangeableWrapper exporter = bounds.get(key);
            if (exporter == null) {
                logger.warn(new IllegalStateException("error state, exporter should not be null"));
                return;
            }
            //The current, may have been merged many times
            // 获取当前 URL,当前URL,可能已经合并过很多次,所以并不一定等同于 originUrl 
            URL currentUrl = exporter.getInvoker().getUrl();
            //Merged with this configuration
            // 使用 originUrl 与当前配置合并后,产生新 url,如果动态配置更新了,则新的URl和旧URl肯定不同
            URL newUrl = getConfigedInvokerUrl(configurators, originUrl);
            // 合并服务级别配置
            newUrl = getConfigedInvokerUrl(serviceConfigurationListeners.get(originUrl.getServiceKey())
                    .getConfigurators(), newUrl);
            // 合并应用级别配置
            newUrl = getConfigedInvokerUrl(providerConfigurationListener.getConfigurators(), newUrl);
            // currentUrl != newUrl, 则说明配置有变动,则重新发布服务
            if (!currentUrl.equals(newUrl)) {
            	// 重新发布服务
                RegistryProtocol.this.reExport(originInvoker, newUrl);
				...
            }
        }
1.2 ProviderConfigurationListener

ProviderConfigurationListener 的实现与 ServiceConfigurationListener 基本相同,不同的是,ServiceConfigurationListener 监听的服务级别, ProviderConfigurationListener监听的是应用级别的配置,所以这里监听的节点会有所不同。

  private class ProviderConfigurationListener extends AbstractConfiguratorListener {

        public ProviderConfigurationListener() {
        	// 传入 监听的 key。这里为 {应用名}.configurators
            this.initWith(ApplicationModel.getApplication() + CONFIGURATORS_SUFFIX);
        }


         // 获取现有配置规则,并在导出之前覆盖提供程序URL。当服务发布时会调用此方法来对配置进行刷新。
        private  URL overrideUrl(URL providerUrl) {
            return RegistryProtocol.getConfigedInvokerUrl(configurators, providerUrl);
        }
		
		// 当监听节点有变化时会触发该方法,此方法会调用监听器的doOverrideIfNecessary 方法来刷新本地缓存的配置。
        @Override
        protected void notifyOverrides() {
        	// 遍历所有的 overrideListeners 并调用 doOverrideIfNecessary 来刷新 配置。
            overrideListeners.values().forEach(listener -> ((OverrideListener) listener).doOverrideIfNecessary());
        }
    }

对于应用级别的动态配置,即是是 ZK 作为配置中心也不会出现上面 服务级别动态配置无法触发监听器的情况。

2. RegistryService#subscribe

关于这部分的具体代码,详阅:Dubbo笔记 ⑤ : 服务发布流程 - Protocol#export 中 服务订阅 部分的内容。

我们这里直接说结果 :由于 Dubbo 2.7之前的版本只有注册中心,并没有配置中心和元数据中心,所以动态配置的内容会保存在注册中心上。以 zk 为例,如下图:

RegistryService#subscribe 会监听 图 ① 标注的节点,而上面我们提到,当我们创建服务级别的配置时,dubbo-admin-0.2.0 为了 兼容,会同时创建 图① 和 图 ② 节点。

而我们上面提到 当配置中心是zk 时ServiceConfigurationListener 并不起作用,所以此时的服务级别的动态配置更新就是通过 RegistryService#subscribe 监听 图① 节点的变化完成。

3. 总结

如下表

序号Dubbo版本配置中心服务级别配置应用级别配置12.6 及以下不支持通过 RegistryService#subscribe 完成不支持22.7 及以上ZK通过 RegistryService#subscribe 完成通过 ProviderConfigurationListener 完成32.7 及以上非ZK通过 ServiceConfigurationListener 完成通过 ProviderConfigurationListener 完成

对于 Dubbo 2.6 及以下,通过 ServiceConfigurationListener 和 ProviderConfigurationListener 监听器完成更新。但是对于 zk作为配置中心的场景,ServiceConfigurationListener 失效,通过 Dubbo 2.6 版本的旧逻辑来完成更新。

对于 Dubbo 2.7及以上,通过 RegistryService#subscribe 方法中的逻辑完成更新。


应用配置和服务级别配置的优先级为: 应用级别 > 服务级别

因为配置的刷新是通过 RegistryProtocol.OverrideListener#notify 方法完成,在这个方法中会先 加载 ServiceConfigurationListeners 的配置,后加载 ProviderConfigurationListener 的配置。即后加载的配置会覆盖之前加载的配置,故应用级别 > 服务级别。

三、消费者

消费者的流程和提供者相似,下面来简单介绍。

当消费者启动时,Dubbo 会按照如下流程执行:

RegistryProtocol#refer ->  RegistryProtocol#doRefer ->  RegistryDirectory#subscribe

其中 RegistryDirectory#subscribe 的实现如下:

	private static final ConsumerConfigurationListener consumerConfigurationListener = new ConsumerConfigurationListener();
    private ReferenceConfigurationListener serviceConfigurationListener;
    
    public void subscribe(URL url) {
        setConsumerUrl(url);
        // 1. 订阅应用级别配置
        consumerConfigurationListener.addNotifyListener(this);
        // 2. 订阅当前引用的接口服务的服务级别配置
        serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
        // 3. Dubbo 2.6 版本的订阅方式
        registry.subscribe(url, this);
    }

这里我们又看到了两个监听器,我们直接说明:

  1. RegistryDirectory.ConsumerConfigurationListener#addNotifyListener : Dubbo 2.7 版本新增,订阅当前消费者应用级别配置.
  2. RegistryDirectory.ReferenceConfigurationListener :Dubbo 2.7 版本新增,订阅当前引用的接口服务的配置。这里需要对于提供者只需要关注自身的动态配置即可,但是对于消费者则需要订阅包括提供者列表 providers,动态配置信息 configurators,路由配置 routers 的信息。
  3. RegistryService#subscribe : Dubbo 2.6 版本旧逻辑,订阅当前引用的服务的配置,包括提供者列表 providers,动态配置信息 configurators,路由配置 routers 的信息。

注: 对于 路由节点,消费者会在 ListenableRouter#init 和 TagRouter#notify 方法中也会调用 DynamicConfiguration#addListener 来增加监听器进行监听。


RegistryService#subscribe 我们在上面已经提到过,需要注意的是 对于消费者来说RegistryDirectory#subscribe 不仅仅只订阅了configurators,而是订阅了 providers、configurators、routers 节点,当providers、configurators、routers 子节点变化时会通过回调通知RegistryDirectory。

下面我们来看 ConsumerConfigurationListener 和 ReferenceConfigurationListener 的实现。

1. ConsumerConfigurationListener & ReferenceConfigurationListener

ConsumerConfigurationListener和 ReferenceConfigurationListener 都是 RegistryDirectory 的内部静态类,其实现基本相同,如下:

    private static class ConsumerConfigurationListener extends AbstractConfiguratorListener {
        List listeners = new ArrayList<>();

        ConsumerConfigurationListener() {
        	// 应用级别监听
            this.initWith(ApplicationModel.getApplication() + Constants.CONFIGURATORS_SUFFIX);
        }
		// 添加监听器
        void addNotifyListener(RegistryDirectory listener) {
            this.listeners.add(listener);
        }

        @Override
        protected void notifyOverrides() {
        	// 当消费者的应用级别配置更新时会遍历所有监听器进行配置刷新
        	// 即触发 RegistryDirectory#refreshInvoker 来刷新本地 
            listeners.forEach(listener -> listener.refreshInvoker(Collections.emptyList()));
        }
    }
    
    private static class ReferenceConfigurationListener extends AbstractConfiguratorListener {
        private RegistryDirectory directory;
        private URL url;

        ReferenceConfigurationListener(RegistryDirectory directory, URL url) {
            this.directory = directory;
            this.url = url;
            // 服务级别监听
            this.initWith(url.getEncodedServiceKey() + Constants.CONFIGURATORS_SUFFIX);
        }

        @Override
        protected void notifyOverrides() {
            // to notify configurator/router changes
            // 刷新引用的服务配置
            // 触发 RegistryDirectory#refreshInvoker 来刷新本地
            directory.refreshInvoker(Collections.emptyList());
        }
    }

当动态配置更新后,会触发 RegistryDirectory#refreshInvoker 来刷新本地配置,按照如下流程调用后 来到了RegistryDirectory#mergeUr 方法中。

RegistryDirectory#refreshInvoker -> RegistryDirectory#toInvokers -> RegistryDirectory#mergeUrl 
1.1 RegistryDirectory#mergeUrl

在 RegistryDirectory#mergeUrl 方法中 Dubbo 完成了配置合并,合并的优先级是

动态配置 > Jvm 本地配置 > 消费者端配置(xml > Properties) > 提供者端配合

下面我们来看具体实现:

    
    private URL mergeUrl(URL providerUrl) {
    	// providerUrl 是 提供者端, queryMap 是消费者端的参数
    	// 1. 合并消费者端的参数。这一步会使用消费者参数覆盖提供者参数
        providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // Merge the consumer side parameters
		// 2. 动态配置的参数合并
        providerUrl = overrideWithConfigurator(providerUrl);
		
        providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); // Do not check whether the connection is successful or not, always create Invoker!

        // The combination of directoryUrl and override is at the end of notify, which can't be handled here
        this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters()); // Merge the provider side parameters

        if ((providerUrl.getPath() == null || providerUrl.getPath()
                .length() == 0) && "dubbo".equals(providerUrl.getProtocol())) { // Compatible version 1.0
            //fix by tony.chenl DUBBO-44
            String path = directoryUrl.getParameter(Constants.INTERFACE_KEY);
            if (path != null) {
                int i = path.indexOf('/');
                if (i >= 0) {
                    path = path.substring(i + 1);
                }
                i = path.lastIndexOf(':');
                if (i >= 0) {
                    path = path.substring(0, i);
                }
                providerUrl = providerUrl.setPath(path);
            }
        }
        return providerUrl;
    }
    
	// 2. 覆盖参数配置
    private URL overrideWithConfigurator(URL providerUrl) {
        // override url with configurator from "override://" URL for dubbo 2.6 and before
        // 2.1 Dubbo 2.6 及以前的版本的覆盖策略 
        providerUrl = overrideWithConfigurators(this.configurators, providerUrl);

        // override url with configurator from configurator from "app-name.configurators"
        // 2.2 Dubbo 2.7 版本,通过 consumerConfigurationListener 监听动态消费者应用级别配置,在此进行配置合并
        providerUrl = overrideWithConfigurators(consumerConfigurationListener.getConfigurators(), providerUrl);

        // override url with configurator from configurators from "service-name.configurators"
        // 2.3 提供者服务级别配置合并
        if (serviceConfigurationListener != null) {
            providerUrl = overrideWithConfigurators(serviceConfigurationListener.getConfigurators(), providerUrl);
        }

        return providerUrl;
    }

我们这里假设 消费者端 本地的原始配置为 URL0,则 :

  1. 在消费者启动时,会加载 JVM 配置,并覆盖本地配置 URL0。RegistryDirectory 在初始化时会将合并后的消费者配置保存到 RegistryDirectory#queryMap 中。这一步完成了 Jvm 配置 覆盖 消费者端本地配置 ,诞生了合并后的新配置 URL1。
  2. 在上面的 RegistryDirectory#mergeUrl 的代码中, ClusterUtils.mergeUrl(providerUrl, queryMap); 会使用 queryMap 中的配置覆盖providerUrl 中的配置,而 queryMap 中保存的是URL1,providerUrl 为从注册中心拉取的提供者的配置。这一步完成了 URL1 覆盖提供者的配置,诞生了合并后的新配置 URL2.
  3. 在随后的 RegistryDirectory#overrideWithConfigurator 方法中,会拉取动态配置并覆盖 URL2 的配置,这一步完成了动态配置 覆盖 URL2,诞生了合并后的最终配合 URL3。

综合上面的过程得到优先级的合并规则 :

动态配置 > Jvm 本地配置 > 消费者端配置(xml > Properties) > 提供者端配合

以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://dubbo.apache.org/zh/docs/v2.7/dev/source/
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5671669.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存