Dubbo服务治理(v2.7.x)-覆盖规则

793 阅读2分钟

一、覆盖规则概念

覆盖规则是Dubbo设计的在无需重启应用的情况下,动态调整RPC调用行为的一种能力。2.7.0版本开始,支持从服务应用两个粒度来调整动态配置。

二、覆盖规则原理

从2.7.0版本开始,覆盖规则主要依靠一个动态配置类和四个监听器来完成。目前支持得动态配置组件有:

大家可以参考Dubbo是如何利用Apollo和Zookeeper实现节点的监听的。

ServiceConfigurationListenerProviderConfigurationListener在服务发布的时候被初始化。ConsumerConfigurationListenerReferenceConfigurationListener在服务引用的时候被初始化。

  • 当Provider的Url发生变化的时候会通过监听器监听到服务的URL发生的变化,如若和注册中心的不一致,就重新暴露服务。
    -> RegistryProtocol.ServiceConfigurationListener#notifyOverrides()
      -> RegistryProtocol.OverrideListener#doOverrideIfNecessary()
         -> RegistryProtocol#reExport() // 重新暴露服务 
    
  • 当Consumer的Url发生变化的时候监听器监听到变化后,会通过服务字典重新刷新Invoker列表。
    -> RegistryDirectory.ReferenceConfigurationListener#notifyOverrides()
      -> RegistryDirectory#refreshInvoker()
    

三、如何书写覆盖规则

dubbo.apache.org/zh-cn/docs/…

我们只要按照Dubbo文档提供的来配置就可以了,但是有几个地方是Dubbo文档中没有写明的,我们还需要注意一下。

public synchronized void doOverrideIfNecessary() {
            final Invoker<?> invoker;
            if (originInvoker instanceof InvokerDelegate) {
                invoker = ((InvokerDelegate<?>) originInvoker).getInvoker();
            } else {
                invoker = originInvoker;
            }
            // 已经注册服务的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 currentUrl = exporter.getInvoker().getUrl();
            //Merged with this configuration
            // 兼容旧版本
            URL newUrl = getConfigedInvokerUrl(configurators, originUrl);
            // 获取服务粒度配置
            newUrl = getConfigedInvokerUrl(serviceConfigurationListeners.get(originUrl.getServiceKey())
                    .getConfigurators(), newUrl);
            // 获取应用粒度配置
            newUrl = getConfigedInvokerUrl(providerConfigurationListener.getConfigurators(), newUrl);
            if (!currentUrl.equals(newUrl)) {
                RegistryProtocol.this.reExport(originInvoker, newUrl);
                logger.info("exported provider url changed, origin url: " + originUrl +
                        ", old export url: " + currentUrl + ", new export url: " + newUrl);
            }
        }
 -> RegistryProtocol#getConfigedInvokerUrl
  -> AbstractConfigurator#configure
public URL configure(URL url) {
        // If override url is not enabled or is invalid, just return.
        if (!configuratorUrl.getParameter(Constants.ENABLED_KEY, true) || configuratorUrl.getHost() == null || url == null || url.getHost() == null) {
            return url;
        }
        
        // 获取apiVersion
        String apiVersion = configuratorUrl.getParameter(Constants.CONFIG_VERSION_KEY);
        if (StringUtils.isNotEmpty(apiVersion)) {
            // 获取当前url side
            String currentSide = url.getParameter(Constants.SIDE_KEY);
            // 获取变更url side
            String configuratorSide = configuratorUrl.getParameter(Constants.SIDE_KEY);
            // 变更url的端口号为[0],变更所有消费者实例
            if (currentSide.equals(configuratorSide) && Constants.CONSUMER.equals(configuratorSide) && 0 == configuratorUrl.getPort()) {
                url = configureIfMatch(NetUtils.getLocalHost(), url);
                // 根据端口号变更服务提供者
            } else if (currentSide.equals(configuratorSide) && Constants.PROVIDER.equals(configuratorSide) && url.getPort() == configuratorUrl.getPort()) {
                url = configureIfMatch(url.getHost(), url);
            }
        }
        /**
         * This else branch is deprecated and is left only to keep compatibility with versions before 2.7.0
         */
        else {
            url = configureDeprecated(url);
        }
        return url;
    }

四、总结

  1. 不同的规则以不同的key后缀区分:

    • configurators - 覆盖规则
    • tag-router - 标签路由
    • condition-router - 条件路由
  2. 如果服务进行了分组和版本区分:

     key: group*service:version.configurators
    
  3. 2.7.0版本以后配置内容必须添加:

    configVersion: v2.7
    
  4. Provider必须指定端口号指令实例

  5. Consumer作用于所有实例

    建议直接配置 addresses : [0.0.0.0]