Eureka详解系列(三)–探索Eureka强大的配置体系

通过前面的两篇博客,我们知道了:什么是 Eureka?为什么使用 Eureka?如何使用 Eureka?今天,我们开始来研究 Eureka 的源码,先从配置部分的源码开始看,其他部分后面再补充。

补充一点,我更多地会从设计层面分析源码,而不会顺序地剖析每个过程的代码。一方面是因为篇幅有限,另一方面是因为我认为这样做更有意义一些。

os:win 10

jdk:1.8.0_231

eureka:1.10.11

maven:3.6.3

ConcurrentCompositeConfiguration 这个类是 Eureka 配置体系的核心。在这个例子中,我们使用它 对 property 进行增删改查,并 注册了自定义监听器来监听 property 的改变

    @Test
    public void test01() {
        // 创建配置对象
        final ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
        // 注册监听器监听property的改变
        config.addConfigurationListener(new ConfigurationListener() {

            public void configurationChanged(ConfigurationEvent event) {
                // 增加property
                if(AbstractConfiguration.EVENT_ADD_PROPERTY == event.getType()
                        && !event.isBeforeUpdate()) {
                    System.err.println("add property:" + event.getPropertyName() + "=" + event.getPropertyValue());
                    return;
                }
                // 删除property
                if(AbstractConfiguration.EVENT_CLEAR_PROPERTY == event.getType()) {
                    System.err.println("clear property:" + event.getPropertyName());
                    return;
                }
                // 更新property
                if(AbstractConfiguration.EVENT_SET_PROPERTY == event.getType()
                        && event.isBeforeUpdate()
                        && !config.getString(event.getPropertyName()).equals(event.getPropertyValue())) {
                    System.err.println("update property:"
                    + event.getPropertyName()
                    + ":"
                    + config.getString(event.getPropertyName())
                    + "==>"
                    + event.getPropertyValue()
                    );
                    return;
                }
            }
        });
        // 添加property
        config.addProperty("author", "zzs");
        // 获取property
        System.err.println(config.getString("author"));
        // 更改property
        config.setProperty("author", "zzf");
        // 删除property
        config.clearProperty("author");
    }
//    运行以上方法,控制台打印内容:
//    add property:author=zzs
//    zzs
//    update property:author:zzs==>zzf
//    clear property:author

可以看到,当我们更改了 property 时,监听器中的方法被触发了,利用这一点,我们可以实现动态配置。

后面就会发现, Eureka 底层使用 ConcurrentCompositeConfiguration 来对配置参数进行增删改查,并基于事件监听的机制来支持动态配置

我们再来看看一个 UML 图。上面例子中说到 ConcurrentCompositeConfiguration的两个功能,是通过实现 Configuration和继承 EventSource来获得的,这一点没什么特别的,之所以深究它,是因为我发现了其他有趣的地方。

我们主要来关注下它的三个成员属性(它们都是 AbstractConfiguration类型):

为了更好理解它们的作用,我写了个测试例子。

    @Test
    public void test02() {
        // 创建配置对象
        ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
        // 添加配置1
        ConcurrentMapConfiguration config1 = new ConcurrentMapConfiguration();
        config1.addProperty("author", "zzs");
        config.addConfiguration(config1, "CONFIG_01");

        // 添加配置2
        ConcurrentMapConfiguration config2 = new ConcurrentMapConfiguration();
        config2.addProperty("author", "zzf");
        config.addConfiguration(config2, "CONFIG_02");

        // 在默认的containerConfiguration中添加property
        config.addProperty("author", "zhw");

        // ============以下测试configList的优先级============
        System.err.println(config.getString("author"));
        // 删除config1中的property
        config1.clearProperty("author");
        System.err.println(config.getString("author"));
        // 删除config2中的property
        config2.clearProperty("author");
        System.err.println(config.getString("author"));

        // ============以下测试overrideProperties的优先级============
        // 添加overrideProperties的property
        config.setOverrideProperty("author", "lt");
        System.err.println(config.getString("author"));
    }
//    运行以上方法,控制台打印内容:
//    zzs
//    zzf
//    zhw
//    lt

这里补充一点,当我们创建 ConcurrentCompositeConfiguration时,就会生成一个 containerConfiguration,默认情况下,它会一直在集合最后面,每次添加新的配置对象,都是往 containerConfiguration 前面插入。

通过上面的例子可以知道, ConcurrentCompositeConfiguration并不会主动地去加载配置,所以,Eureka 需要自己往 ConcurrentCompositeConfiguration里添加配置,而完成这件事的是另外一个类–ConfigurationManager

ConfigurationManager 作为一个单例对象使用,用来初始化配置对象,以及提供加载配置文件的方法(后面的 DefaultEurekaClientConfigDefaultEurekaServerConfig会来调用这些方法)。

    private static AbstractConfiguration createDefaultConfigInstance() {
        ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
        try {
            // 加载指定url的配置
            // 通过archaius.configurationSource.additionalUrls启动参数设置url,多个逗号隔开
            DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
            config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
        } catch (Throwable e) {
            logger.warn("Failed to create default dynamic configuration", e);
        }
        if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
            // 加载System.getProperties()的配置
            // 通过archaius.dynamicProperty.disableSystemConfig启动参数可以控制是否添加
            SystemConfiguration sysConfig = new SystemConfiguration();
            config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
        }
        if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
            // 加载System.getenv()的配置
            // 通过archaius.dynamicProperty.disableEnvironmentConfig启动参数可以控制是否添加
            EnvironmentConfiguration envConfig = new EnvironmentConfiguration();
            config.addConfiguration(envConfig, ENV_CONFIG_NAME);
        }
        // 这个是自定义的保底配置
        ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
        config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
        config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));// 这里可以更改保底配置
        return config;
    }

可以看到, Eureka 支持通过 url 来指定配置文件,只要指定启动参数就行,这一点将有利于我们更灵活地对项目进行配置。默认情况下,它还会去加载所有的系统参数和环境参数。

另外,当我们设置以下启动参数,就可以通过 JMX 的方式来更改配置。

-Darchaius.dynamicPropertyFactory.registerConfigWithJMX=true

配置对象初始化后, ConfigurationManager提供了方法供我们加载配置文件(本地或远程),如下。

// 这两个的区别在于:前者会生成一个新的配置添加到configList;后者直接将property都加入到appOverrideConfig
public static void loadCascadedPropertiesFromResources(String configName) throws IOException;
public static void loadAppOverrideProperties(String appConfigName);

动态配置的内容直接看源码不大好理解,我们先通过一个再简单不过的例子开始来一步步实现我们自己的动态配置。在下面的方法中,我更改了 property,但是拿不到更新的值。原因嘛,我相信大家都知道。

    @Test
    public void test03() {
        // 获取配置对象
        AbstractConfiguration config = ConfigurationManager.getConfigInstance();
        // 添加一个property
        config.addProperty("author", "zzs");

        String author = config.getString("author", "");

        System.err.println(author);

        // 更改property
        config.setProperty("author", "zzf");

        System.err.println(author);
    }
//    运行以上方法,控制台打印内容:
//    zzs
//    zzs

为了拿到更新的值,我把代码改成这样。我不定义变量来存放 property 的值,每次都重新获取。显然,这样做可以成功。

    @Test
    public void test04() {
        // 获取配置对象
        AbstractConfiguration config = ConfigurationManager.getConfigInstance();
        // 添加一个property
        config.addProperty("author", "zzs");

        System.err.println(config.getString("author", ""));

        // 更改property
        config.setProperty("author", "zzf");

        System.err.println(config.getString("author", ""));
    }
//    运行以上方法,控制台打印内容:
//    zzs
//    zzf

但是上面的做法有个问题,我们都知道从 ConcurrentCompositeConfiguration中获取 property 是比较麻烦的,因为我需要去遍历 configList,以及进行参数的转换等。每次都这样拿,不大合理。

于是,我增加了缓存来减少这部分的开销,当然,property 更改时我必须刷新缓存。

    @Test
    public void test05() {
        // 缓存
        Map cache = new ConcurrentHashMap();
        // 获取配置对象
        AbstractConfiguration config = ConfigurationManager.getConfigInstance();
        // 添加一个property
        config.addProperty("author", "zzs");

        String value = cache.computeIfAbsent("author", x -> config.getString(x, ""));
        System.err.println(value);

        // 添加监听器监听property的更改
        config.addConfigurationListener(new ConfigurationListener() {
            public void configurationChanged(ConfigurationEvent event) {
                // 删除property
                if(AbstractConfiguration.EVENT_CLEAR_PROPERTY == event.getType()) {
                    cache.remove(event.getPropertyName());
                    return;
                }
                // 更新property
                if(AbstractConfiguration.EVENT_SET_PROPERTY == event.getType()
                        && !event.isBeforeUpdate()) {
                    cache.put(event.getPropertyName(), String.valueOf(event.getPropertyValue()));
                    return;
                }
            }
        });

        // 更改property
        config.setProperty("author", "zzf");

        System.err.println(cache.get("author"));
    }
//    运行以上方法,控制台打印内容:
//    zzs
//    zzf

通过上面的例子,我们实现了动态配置。

现在我们再来看看 Eureka 是怎么实现的。这里用到了 DynamicPropertyFactoryDynamicStringProperty两个类,通过它们,也实现了动态配置。

    @Test
    public void test06() {
        // 获取配置对象
        AbstractConfiguration config = ConfigurationManager.getConfigInstance();
        // 添加一个property
        config.addProperty("author", "zzs");

        // 通过DynamicPropertyFactory获取property
        DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();
        DynamicStringProperty stringProperty = dynamicPropertyFactory.getStringProperty("author", "");

        System.err.println(stringProperty.get());

        // 更改property
        config.setProperty("author", "zzf");

        System.err.println(stringProperty.get());
    }
//    运行以上方法,控制台打印内容:
//    zzs
//    zzf

至于原理,其实和我们上面的例子是差不多的。通过 UML 图可以知道, DynamicProperty中就放了一张缓存表,每次获取 property 时,会优先从这里拿。

既然有缓存,就应该有监听器,没错,在 DynamicProperty.initialize(DynamicPropertySupport)方法中就可以看到。

    static synchronized void initialize(DynamicPropertySupport config) {
        dynamicPropertySupportImpl = config;
        // 注册监听器
        config.addConfigurationListener(new DynamicPropertyListener());
        updateAllProperties();
    }

在上面的分析中,我们用 ConfigurationManager来初始化配置对象,并使用 DynamicPropertyFactory来实现动态配置,这些东西构成了 Eureka 的配置体系的基础,比较通用。基础之上,是 Eureka 更具体的一些配置对象。

在 Eureka 里,配置分成了三种(理解这一点非常重要):

这三个对象都持有了 DynamicPropertyFactory的引用,所以支持动态配置,另外,它们还是用 ConfigurationManager来加载自己想要的配置文件。例如, EurekaInstanceConfigEurekaClientConfig负责加载 eureka-client.properties,而 EurekaServerConfig则负责加载 eureka-server.properties

以上基本讲完 Eureka 配置体系的源码,可以看到,这是一套非常优秀的配置体系,实际项目中可以参考借鉴。

最后,感谢阅读。

Original: https://www.cnblogs.com/ZhangZiSheng001/p/14374005.html
Author: 子月生
Title: Eureka详解系列(三)–探索Eureka强大的配置体系

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/575798/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • rm: cannot remove ‘/var/lock/subsys/mysql’: Permission denied

    Lock directory for RedHat / SuSE. lockdir=’/var/lock/subsys’ lock_file_path="$lockdir…

    数据库 2023年6月11日
    062
  • Dapper.FastCRUD与Dapper中的CustomPropertyTypeMap冲突

    在使用Dapper.NET时,由于生成的实体的属性与数据库表字段不同(如表字段叫USER_NAME,生成的对应的实体属性则为UserName)。 这时需要使用Dapper中的Cus…

    数据库 2023年6月14日
    085
  • MySQL实战45讲 12

    12 | 为什么我的MySQL会”抖”一下? 一条 SQL 语句,正常执行的时候特别快,但是 有时也不知道怎么回事,它就会变得特别慢,并且这样的场景很难复现…

    数据库 2023年6月16日
    081
  • MySQL多表查询

    多表查询 案列说明 笛卡尔积的理解 select id,department_name from employees,departments;#错的 select id,depar…

    数据库 2023年5月24日
    073
  • Qingcloud_MySQL Plus(Xenon) 高可用搭建实验

    实验:Xenon on 5.7.30 Xenon (MySQL Plus) 是青云Qingcloud的一个开源项目,号称金融级别强一致性的高可用解决方案,项目地址为 https:/…

    数据库 2023年6月16日
    0111
  • 一句话的需求怎么测?需求文档的三种现状及应对策略

    转载请注明出处❤️ 你好,我是测试蔡坨坨。 今天,我们来聊聊需求文档那些事儿…… 众所周知,软件需求是软件项目研发的开始,是组建研发团队后第一次集体讨论的事…

    数据库 2023年6月11日
    078
  • Spring框架完整学习!!!

    1.Spring 1.1、简介 1.2、优点 1.3、组成 1.4、拓展 2.IOC思想解析 2.1、场景模拟 2.2、概念解析 2.3、总结 3.初涉Spring 3.1、 基础…

    数据库 2023年6月16日
    086
  • 一文了解Cookie

    Cookie 什么是 Cookie? 先要了解HTTP是 无状态的Web服务器,什么是无状态呢?一次对话完成后下一次对话完全不知道上一次对话发生了什么。如果在Web服务器中只是用来…

    数据库 2023年6月11日
    089
  • Oracle 备份与恢复 (Docker部署版)

    Oracle 备份与恢复 (Docker部署版) 一,宿主机设置定时备份脚本 1.检查Oracle容器是否正常运行 docker ps 2.进入容器,创建shell脚本 #orac…

    数据库 2023年6月11日
    081
  • Celery异步任务

    (1)安装celery pip install celery==4.2.1 (2)celery使用 在项目适当位置创建celery_tasks目录用于保存celery异步任务。 在…

    数据库 2023年6月14日
    062
  • centos下安装jdk8和maven

    创建目录 mkdir -p/usr/local/java/ 网盘拉去JDK和Maven 链接:https://pan.baidu.com/s/1GgJk8ji0r-tjGAj_ea…

    数据库 2023年6月6日
    065
  • JDBC

    JDBC 一、JDBC概述 什么是JDBC? JDBC 是使用 Java 语言操作关系型数据库的一套 API。这套 API 是交由不同的数据库厂商实现的。我们利用 JDBC 编写操…

    数据库 2023年5月24日
    074
  • Spring源码分析-BeanFactoryPostProcessor

    Spring源码分析-BeanFactoryPostProcessor 博主技术有限,本文难免有错误的地方,如果您发现了欢迎评论私信指出,谢谢JAVA技术交流群:737698533…

    数据库 2023年6月16日
    089
  • Linux 7安装Mysql5.7版本

    Mysql 5.7的安装搭建 首先去到官方网站的下载链接中找到对应你Linux服务器版本的mysql软件包 https://dev.mysql.com/downloads/repo…

    数据库 2023年5月24日
    086
  • Spark学习(1) Spark入门

    什么事spark Spark是一种快速、通用、可扩展的大数据计算引擎.项目是用Scala进行编写,基于内存计算的 包括交互式查询和流处理 spark内置项目 Spark SQL:是…

    数据库 2023年6月16日
    085
  • MySQL 索引排序

    表结构和数据 CREATE TABLE t1 ( id int(11) NOT NULL AUTO_INCREMENT, a int(11) DEFAULT NULL, b int…

    数据库 2023年5月24日
    068
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球