• Dubbo-Dubbo 动态配置中心


    Dubbo 动态配置中心

    一、参考文档

    http://dubbo.apache.org/zh-cn/docs/user/configuration/config-center.html

    三大中心指的:注册中心,元数据中心,配置中心。
    在 2.7 之前的版本,Dubbo 只配备了注册中心,主流使用的注册中心为 zookeeper。新增加了元数据中心和配置中心,自然是为了解决对应的痛点,下面我们来详细阐释三大中心改造的原因。

    元数据改造

    元数据是什么?元数据定义为描述数据的数据,在服务治理中,例如服务接口名,重试次数,版本号等等都可以理解为元数据。在 2.7 之前,元数据一股脑丢在了注册中心之中,这造成了一系列的问题:
    推送量大 -> 存储数据量大 -> 网络传输量大 -> 延迟严重
    生产者端注册 30+ 参数,有接近一半是不需要作为注册中心进行传递;消费者端注册 25+ 参数,只有个别需要传递给注册中心。有了以上的理论分析,Dubbo 2.7 进行了大刀阔斧的改动,只将真正属于服务治理的数据发布到注册中心之中,大大降低了注册中心的负荷。
    同时,将全量的元数据发布到另外的组件中:元数据中心。元数据中心目前支持 redis(推荐),zookeeper。这也为 Dubbo 2.7 全新的 Dubbo Admin 做了准备,关于新版的 Dubbo Admin,我将会后续准备一篇独立的文章进行介绍。
    示例:使用 zookeeper 作为元数据中心

    Dubbo 2.6 元数据

    dubbo://30.5.120.185:20880/com.alibaba.dubbo.demo.DemoService
    anyhost=true&
    application=demo-provider&
    interface=com.alibaba.dubbo.demo.DemoService&
    methods=sayHello&
    bean.name=com.alibaba.dubbo.demo.DemoService&
    dubbo=2.0.2&
    executes=4500&
    generic=false&
    owner=kirito&
    pid=84228&
    retries=7&
    side=provider&
    timestamp=1552965771067
    从本地的 zookeeper 中取出一条服务数据,通过解码之后,可以看出,的确有很多参数是不必要。

    Dubbo 2.7 元数据

    在 2.7 中,如果不进行额外的配置,zookeeper 中的数据格式仍然会和 Dubbo 2.6 保持一致,这主要是为了保证兼容性,让 Dubbo 2.6 的客户端可以调用 Dubbo 2.7 的服务端。如果整体迁移到 2.7,则可以为注册中心开启简化配置的参数:

    Dubbo 将会只上传那些必要的服务治理数据,一个简化过后的数据如下所示:
    dubbo://30.5.120.185:20880/org.apache.dubbo.demo.api.DemoService
    application=demo-provider&
    dubbo=2.0.2&
    release=2.7.0&
    timestamp=1552975501873
    对于那些非必要的服务信息,仍然全量存储在元数据中心之中:

    元数据中心的数据可以被用于服务测试,服务 MOCK 等功能。目前注册中心配置中 simplified 的默认值为 false,因为考虑到了迁移的兼容问题,在后续迭代中,默认值将会改为 true。

    配置中心支持

    衡量配置中心的必要性往往从三个角度出发:

    1. 分布式配置统一管理

    2. 动态变更推送

    3. 安全性

    Spring Cloud Config, Apollo, Nacos 等分布式配置中心组件都对上述功能有不同程度的支持。在 2.7 之前的版本中,在 zookeeper 中设置了部分节点:configurators,routers,用于管理部分配置和路由信息,它们可以理解为 Dubbo 配置中心的雏形。在 2.7 中,Dubbo 正式支持了配置中心,目前支持的几种注册中心 Zookeeper,Apollo,Nacos(2.7.1-release 支持)。
    在 Dubbo 中,配置中心主要承担了两个作用

    • 外部化配置。启动配置的集中式存储

    • 服务治理。服务治理规则的存储与通知

    示例:使用 Zookeeper 作为配置中心

    引入配置中心后,需要注意配置项的覆盖问题。

    二、动态配置中心

    何为配置中心?配置中心即就是我们平常经常见的比如注册中心的地址,服务的版本,服务的分组,反正在dubbo中能够看到的一些信息都可以作为配置中心中去存储。官方说的这两点比较的明确。就是一些外部化的参数配置,其实这个在其他的配置中心组件中非常常见,比如diamond 从remote获取配置信息,然后覆盖本地的信息。
    image.png

    1、直接下载官方demo工程

    git clone   https://github.com/apache/dubbo-samples.git
    
    • 1

    dubbo-samples-zookeeper

    spring/dubbo-provider.properties
    将本地的配置修改为配置中心的地址

    dubbo.application.name=zookeeper-demo-provider
    dubbo.config-center.address=zookeeper://127.0.0.1:2181
    
    #dubbo.registry.address=zookeeper://${zookeeper.address:localhost}:2181
    #dubbo.protocol.name=dubbo
    #dubbo.protocol.port=20880
    #dubbo.application.qosEnable=true
    #dubbo.application.qosPort=33333
    dubbo.application.qosAcceptForeignIp=false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、配置管理

    Dubbo admin 配置中
    image.png
    将一些基础配置信息配置进去

    dubbo.registry.address=zookeeper://127.0.0.1:2181 
    dubbo.metadata-report.address=zookeeper://127.0.0.1:2181
    dubbo.protocol.name=dubbo
    dubbo.protocol.port=20880
    dubbo.application.qosEnable=true
    dubbo.application.qosPort=33333
    dubbo.application.qosAcceptForeignIp=false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3、查看 zookeeper

    image.png

    4、启动demo服务即可。

    发现demo 发现日志监听配置的变化

    12/07/19 10:35:23:023 CST] main-EventThread  INFO state.ConnectionStateManager: State change: CONNECTED
    [12/07/19 10:35:23:023 CST] ZookeeperDynamicConfiguration-thread-1  INFO curator.CuratorZookeeperClient:  [DUBBO] listen the zookeeper changed. The changed data:ChildData{path='/dubbo/config', stat=427,427,1562936763476,1562936763476,0,1,0,0,0,1,428
    , data=[]}, dubbo version: 2.7.2, current host: 192.168.199.112
    [12/07/19 10:35:23:023 CST] ZookeeperDynamicConfiguration-thread-1  INFO curator.CuratorZookeeperClient:  [DUBBO] listen the zookeeper changed. The changed data:ChildData{path='/dubbo/config/dubbo', stat=428,428,1562936763478,1562936763478,0,1,0,0,0,1,429
    , data=[]}, dubbo version: 2.7.2, current host: 192.168.199.112
    [12/07/19 10:35:23:023 CST] ZookeeperDynamicConfiguration-thread-1  INFO curator.CuratorZookeeperClient:  [DUBBO] listen the zookeeper changed. The changed data:ChildData{path='/dubbo/config/dubbo/dubbo.properties', stat=429,465,1562936763479,1562940541881,4,0,0,0,267,0,429
    , data=[100, 117, 98, 98, 111, 46, 114, 101, 103, 105, 115, 116, 114, 121, 46, 97, 100, 100, 114, 101, 115, 115, 61, 122, 111, 111, 107, 101, 101, 112, 101, 114, 58, 47, 47, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 50, 49, 56, 49, 32, 10, 100, 117, 98, 98, 111, 46, 109, 101, 116, 97, 100, 97, 116, 97, 45, 114, 101, 112, 111, 114, 116, 46, 97, 100, 100, 114, 101, 115, 115, 61, 122, 111, 111, 107, 101, 101, 112, 101, 114, 58, 47, 47, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 50, 49, 56, 49, 10, 100, 117, 98, 98, 111, 46, 112, 114, 111, 116, 111, 99, 111, 108, 46, 110, 97, 109, 101, 61, 100, 117, 98, 98, 111, 10, 100, 117, 98, 98, 111, 46, 112, 114, 111, 116, 111, 99, 111, 108, 46, 112, 111, 114, 116, 61, 50, 48, 56, 56, 48, 10, 100, 117, 98, 98, 111, 46, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 46, 113, 111, 115, 69, 110, 97, 98, 108, 101, 61, 116, 114, 117, 101, 10, 100, 117, 98, 98, 111, 46, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 46, 113, 111, 115, 80, 111, 114, 116, 61, 51, 51, 51, 51, 51, 10, 100, 117, 98, 98, 111, 46, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 46, 113, 111, 115, 65, 99, 99, 101, 112, 116, 70, 111, 114, 101, 105, 103, 110, 73, 112, 61, 102, 97, 108, 115, 101]}, dubbo version: 2.7.2, current host: 192.168.199.112
    [12/07/19 10:35:23:023 CST] ZookeeperDynamicConfiguration-thread-1  INFO curator.CuratorZookeeperClient:  [DUBBO] listen the zookeeper changed. The changed data:null, dubbo version: 2.7.2, current host: 192.168.199.112
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    全局的额陪孩子和官方文档相同处理逻辑。
    image.png

    5、实现原理

    在这里插入图片描述

    三、源码解析

    1、服务导出

    监听到spring 启动完毕,然后服务进行导出
    org.apache.dubbo.config.spring.ServiceBean#onApplicationEvent
    org.apache.dubbo.config.spring.ServiceBean#export

     public void onApplicationEvent(ContextRefreshedEvent event) {
            if (!isExported() && !isUnexported()) {
                if (logger.isInfoEnabled()) {
                    logger.info("The service ready on spring started. service: " + getInterface());
                }
                export();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    org.apache.dubbo.config.ServiceConfig#export
    这里是同步的进行导出服务,先检查配置项信息。

    public synchronized void export() {
            checkAndUpdateSubConfigs();
    
            if (!shouldExport()) {
                return;
            }
    
            if (shouldDelay()) {
                delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
            } else {
                doExport();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2、获取配置流程

    org.apache.dubbo.config.ServiceConfig#checkAndUpdateSubConfigs

    1. 获取本地的全局配置,比如应用、注册中心、协议、配置中心地址等等

    2. 从配置中心获取信息

      public void checkAndUpdateSubConfigs() {
      // Use default configs defined explicitly on global configs
      completeCompoundConfigs();
      // Config Center should always being started first.
      startConfigCenter();
      checkDefault();
      checkProtocol();
      checkApplication();
      // if protocol is not injvm checkRegistry
      if (!isOnlyInJvm()) {
      checkRegistry();
      }
      this.refresh();
      checkMetadataReport();

          if (StringUtils.isEmpty(interfaceName)) {
              throw new IllegalStateException(" interface not allow null!");
          }
      
          if (ref instanceof GenericService) {
              interfaceClass = GenericService.class;
              if (StringUtils.isEmpty(generic)) {
                  generic = Boolean.TRUE.toString();
              }
          } else {
              try {
                  interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                          .getContextClassLoader());
              } catch (ClassNotFoundException e) {
                  throw new IllegalStateException(e.getMessage(), e);
              }
              checkInterfaceAndMethods(interfaceClass, methods);
              checkRef();
              generic = Boolean.FALSE.toString();
          }
          if (local != null) {
              if ("true".equals(local)) {
                  local = interfaceName + "Local";
              }
              Class localClass;
              try {
                  localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
              } catch (ClassNotFoundException e) {
                  throw new IllegalStateException(e.getMessage(), e);
              }
              if (!interfaceClass.isAssignableFrom(localClass)) {
                  throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
              }
          }
          if (stub != null) {
              if ("true".equals(stub)) {
                  stub = interfaceName + "Stub";
              }
              Class stubClass;
              try {
                  stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
              } catch (ClassNotFoundException e) {
                  throw new IllegalStateException(e.getMessage(), e);
              }
              if (!interfaceClass.isAssignableFrom(stubClass)) {
                  throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
              }
          }
          checkStubAndLocal(interfaceClass);
          checkMock(interfaceClass);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51

    3、获取全局配置信息

    org.apache.dubbo.config.ServiceConfig#completeCompoundConfigs

    private void completeCompoundConfigs() {
            if (provider != null) {
                if (application == null) {
                    setApplication(provider.getApplication());
                }
                if (module == null) {
                    setModule(provider.getModule());
                }
                if (registries == null) {
                    setRegistries(provider.getRegistries());
                }
                if (monitor == null) {
                    setMonitor(provider.getMonitor());
                }
                if (protocols == null) {
                    setProtocols(provider.getProtocols());
                }
                if (configCenter == null) {
                    setConfigCenter(provider.getConfigCenter());
                }
            }
            if (module != null) {
                if (registries == null) {
                    setRegistries(module.getRegistries());
                }
                if (monitor == null) {
                    setMonitor(module.getMonitor());
                }
            }
            if (application != null) {
                if (registries == null) {
                    setRegistries(application.getRegistries());
                }
                if (monitor == null) {
                    setMonitor(application.getMonitor());
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    3、获取配置中心

    org.apache.dubbo.config.AbstractInterfaceConfig#startConfigCenter
    环境信息准备是重点。

     void startConfigCenter() {
           ## 配置中心配置是否存在
            if (configCenter == null) {
                ConfigManager.getInstance().getConfigCenter().ifPresent(cc -> this.configCenter = cc);
            }
    
            if (this.configCenter != null) {
                // TODO there may have duplicate refresh
                this.configCenter.refresh();
                ## 准备获环境信息
                prepareEnvironment();
            }
            ConfigManager.getInstance().refreshAll();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4、环境信息准备

    org.apache.dubbo.config.AbstractInterfaceConfig#prepareEnvironment
    image.png

    动态获取配置中心的实现,然后获取到全局配置内容,获取应用配置内容,然后刷新到本地的环境变量中去。

    private void prepareEnvironment() {
            if (configCenter.isValid()) {
                if (!configCenter.checkOrUpdateInited()) {
                    return;
                }
                ## 获取配置中心实现类
                DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl());
                
                ## 获取常量
                String configContent = dynamicConfiguration.getConfigs(configCenter.getConfigFile(), configCenter.getGroup());
    
                String appGroup = application != null ? application.getName() : null;
                String appConfigContent = null;
                if (StringUtils.isNotEmpty(appGroup)) {
                    appConfigContent = dynamicConfiguration.getConfigs
                            (StringUtils.isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(),
                             appGroup
                            );
                }
                try {
                    Environment.getInstance().setConfigCenterFirst(configCenter.isHighestPriority());
                    Environment.getInstance().updateExternalConfigurationMap(parseProperties(configContent));
                    Environment.getInstance().updateAppExternalConfigurationMap(parseProperties(appConfigContent));
                } catch (IOException e) {
                    throw new IllegalStateException("Failed to parse configurations from Config Center.", e);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    5、SPI 动态获取配置工厂

    org.apache.dubbo.config.AbstractInterfaceConfig#getDynamicConfiguration

        private DynamicConfiguration getDynamicConfiguration(URL url) {
            DynamicConfigurationFactory factories = ExtensionLoader
                    .getExtensionLoader(DynamicConfigurationFactory.class)
                    .getExtension(url.getProtocol());
            DynamicConfiguration configuration = factories.getDynamicConfiguration(url);
            Environment.getInstance().setDynamicConfiguration(configuration);
            return configuration;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    image.png

    6、ZookeeperDynamicConfigurationFactory 工厂的实现

    org.apache.dubbo.configcenter.support.zookeeper.ZookeeperDynamicConfigurationFactory
    给予了注册的地址
    image.png

    public class ZookeeperDynamicConfigurationFactory extends AbstractDynamicConfigurationFactory {
    
        private ZookeeperTransporter zookeeperTransporter;
    
        public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
            this.zookeeperTransporter = zookeeperTransporter;
        }
    
    
        @Override
        protected DynamicConfiguration createDynamicConfiguration(URL url) {
            return new ZookeeperDynamicConfiguration(url, zookeeperTransporter);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    7、 ZookeeperDynamicConfiguration连接

    org.apache.dubbo.configcenter.support.zookeeper.ZookeeperDynamicConfiguration#ZookeeperDynamicConfiguration
    这里监听的路径启动

    ZookeeperDynamicConfiguration(URL url, ZookeeperTransporter zookeeperTransporter) {
            this.url = url;
            rootPath = "/" + url.getParameter(CONFIG_NAMESPACE_KEY, DEFAULT_GROUP) + "/config";
    
            initializedLatch = new CountDownLatch(1);
            this.cacheListener = new CacheListener(rootPath, initializedLatch);
            this.executor = Executors.newFixedThreadPool(1, new NamedThreadFactory(this.getClass().getSimpleName(), true));
    
            zkClient = zookeeperTransporter.connect(url);
            zkClient.addDataListener(rootPath, cacheListener, executor);
            try {
                // Wait for connection
                this.initializedLatch.await();
            } catch (InterruptedException e) {
                logger.warn("Failed to build local cache for config center (zookeeper)." + url);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    8、环境信息准备–>获取到配置信息

    org.apache.dubbo.config.AbstractInterfaceConfig#prepareEnvironment
    配置文件、分组都是URL中的参数

     private void prepareEnvironment() {
            if (configCenter.isValid()) {
                if (!configCenter.checkOrUpdateInited()) {
                    return;
                }
                DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl());
                String configContent = dynamicConfiguration.getConfigs(configCenter.getConfigFile(), configCenter.getGroup());
    
                String appGroup = application != null ? application.getName() : null;
                String appConfigContent = null;
                if (StringUtils.isNotEmpty(appGroup)) {
                    appConfigContent = dynamicConfiguration.getConfigs
                            (StringUtils.isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(),
                             appGroup
                            );
                }
                try {
                    Environment.getInstance().setConfigCenterFirst(configCenter.isHighestPriority());
                    Environment.getInstance().updateExternalConfigurationMap(parseProperties(configContent));
                    Environment.getInstance().updateAppExternalConfigurationMap(parseProperties(appConfigContent));
                } catch (IOException e) {
                    throw new IllegalStateException("Failed to parse configurations from Config Center.", e);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    9 读取配置文件

    org.apache.dubbo.common.config.ConfigurationUtils#parseProperties

    public static Map parseProperties(String content) throws IOException {
            Map map = new HashMap<>();
            if (StringUtils.isEmpty(content)) {
                logger.warn("You specified the config centre, but there's not even one single config item in it.");
            } else {
                Properties properties = new Properties();
                properties.load(new StringReader(content));
                properties.stringPropertyNames().forEach(
                        k -> map.put(k, properties.getProperty(k))
                );
            }
            return map;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    整个流程结束.

    四、总结

    整个源码的分析可以看出,整个流程链路比较的清爽、功能划分比较清楚,什么时候该干什么,学习了一波。

  • 相关阅读:
    猿创征文|十 BLE协议之L2CAP
    CATT的应用
    GitHub Universe 2023:AI 技术引领软件开发创新浪潮
    上海华清071班
    qt单例模式
    此处不允许使用特性 setup 报错
    打开GeoTIFF文件失败:Unknown field with tag
    竟然有人把VSCode玩成了IDEA的效果,有点厉害
    Linux常见的命令操作
    深入实现 MyBatis 底层机制的任务阶段 5- 开发和 Mapper 接口相映射的 MapperBean
  • 原文地址:https://blog.csdn.net/m0_67402588/article/details/126327864