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类型):
- configList:持有的配置对象集合。这个集合的配置对象存在优先级,举个例子,如果我添加了 Configuration1 和 Configuration2,当我们
getProperty(String)时,会优先从 Configuration1 获取,实在找不到才会去 Configuration2 获取。 - overrideProperties:最高优先级的配置对象。当我们
getProperty(String)时,会先从这里获取,实在没有才会去 configList 里找。 - containerConfiguration:保底的配置对象。一般是 configList 的最后一个(注意,不一定是最后一个1),我们往
ConcurrentCompositeConfiguration里增删改 property,实际操作的就是这个对象。
为了更好理解它们的作用,我写了个测试例子。
@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作为一个单例对象使用,用来初始化配置对象,以及提供加载配置文件的方法(后面的DefaultEurekaClientConfig、DefaultEurekaServerConfig会来调用这些方法)。
下面我们看看配置对象的初始化。在ConfigurationManager被加载时就会初始化配置对象,进入到它的静态代码块就可以找到。我截取的是最关键部分的代码。
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<String, String> cache = new ConcurrentHashMap<String, String>();
// 获取配置对象
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 是怎么实现的。这里用到了DynamicPropertyFactory和DynamicStringProperty两个类,通过它们,也实现了动态配置。
@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();
}
Eureka有那几类配置
在上面的分析中,我们用ConfigurationManager来初始化配置对象,并使用DynamicPropertyFactory来实现动态配置,这些东西构成了 Eureka 的配置体系的基础,比较通用。基础之上,是 Eureka 更具体的一些配置对象。
在 Eureka 里,配置分成了三种(理解这一点非常重要):
- EurekaInstanceConfig:当前实例身份的配置信息,即我是谁?
- EurekaServerConfig:一些影响当前Eureka Server和客户端或对等节点交互行为的配置信息,即怎么交互?
- EurekaClientConfig:一些影响当前实例和Eureka Server交互行为的配置信息,即和谁交互?怎么交互?
这三个对象都持有了DynamicPropertyFactory的引用,所以支持动态配置,另外,它们还是用ConfigurationManager来加载自己想要的配置文件。例如,EurekaInstanceConfig、EurekaClientConfig负责加载eureka-client.properties,而EurekaServerConfig则负责加载eureka-server.properties。

以上基本讲完 Eureka 配置体系的源码,可以看到,这是一套非常优秀的配置体系,实际项目中可以参考借鉴。
最后,感谢阅读。
参考资料
本文为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/14374005.html
Eureka详解系列(三)--探索Eureka强大的配置体系的更多相关文章
- Eureka详解系列(四)--Eureka Client部分的源码和配置
简介 按照原定的计划,我将分三个部分来分析 Eureka 的源码: Eureka 的配置体系(已经写完,见Eureka详解系列(三)--探索Eureka强大的配置体系): Eureka Client ...
- Eureka详解系列(五)--Eureka Server部分的源码和配置
简介 按照原定的计划,我将分三个部分来分析 Eureka 的源码: Eureka 的配置体系(已经写完,见Eureka详解系列(三)--探索Eureka强大的配置体系): Eureka Client ...
- Java源码详解系列(十二)--Eureka的使用和源码
eureka 是由 Netflix 团队开发的针对中间层服务的负载均衡器,在微服务项目中被广泛使用.相比 SLB.ALB 等负载均衡器,eureka 的服务注册是无状态的,扩展起来非常方便. 在这个系 ...
- Eureka详解系列(二)--如何使用Eureka(原生API,无Spring)
简介 通过上一篇博客 Eureka详解系列(一)--先谈谈负载均衡器 ,我们知道了 Eureka 是什么以及为什么要使用它,今天,我们开始研究如何使用 Eureka. 在此之前,先说明一点.网上几乎所 ...
- Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送
Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送, ...
- Eureka详解系列(一)--先谈谈负载均衡器
这个系列开始研究 Eureka,在此之前,先来谈谈负载均衡器. 本质上,Eureka 就是一个负载均衡器,可能有的人会说,它是一个服务注册中心,用来注册服务的,这种说法不能说错,只是有点片面. 在这篇 ...
- Mybatis源码详解系列(三)--从Mapper接口开始看Mybatis的执行逻辑
简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...
- 源码详解系列(七) ------ 全面讲解logback的使用和源码
什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...
- Mybatis源码详解系列(四)--你不知道的Mybatis用法和细节
简介 这是 Mybatis 系列博客的第四篇,我本来打算详细讲解 mybatis 的配置.映射器.动态 sql 等,但Mybatis官方中文文档对这部分内容的介绍已经足够详细了,有需要的可以直接参考. ...
随机推荐
- Beta冲刺——第六天
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...
- Hbase原理(转学习自用)
一.系统架构 从HBase的架构图上可以看出,HBase中的组件包括Client.Zookeeper.HMaster.HRegionServer.HRegion.Store.MemStore.Stor ...
- 五、Zookeeper、Hbase集群搭建
一.前提 1.安装JDK 2.安装Hadoop 3.安装zoookeeper 1.加入zookeeper包,并解压tar -zxvf zookeeper-3.4.9.tar.gz 2.去/etc/pr ...
- Server 2012 R2 Standard 安装运行PCS7时出现“无法启动此程序,因为计算机中丢失api-ms-win-crt-runtime-l1-1-0.dll”解决方法
网上看到了这篇文章https://www.jianshu.com/p/21f4bb8b5502,根据思路自己尝试,解决了丢失的问题.提示[计算机中丢失api-ms-win-crt-runtime-l1 ...
- 【JavaWeb】AJAX 请求
AJAX 请求 什么是 AJAX AJAX(Asynchronous JavaScript And XMl),即异步 JS 和 XML.是指一种创建交互式网页应用的网页开发技术. AJAX 是一种浏览 ...
- LeetCode374 猜数字大小
我们正在玩一个猜数字游戏. 游戏规则如下:我从 1 到 n 选择一个数字. 你需要猜我选择了哪个数字.每次你猜错了,我会告诉你这个数字是大了还是小了.你调用一个预先定义好的接口 guess(int n ...
- session、cookie、token的区别
从安全性优先级来说: 1.优先级 Cookie<session<token 2. 安全性 Cookie: ①cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺 ...
- java锁的对象引用
当访问共享的可变数据时,通常需要同步.一种避免使用同步的方式就是不共享数据. 如果数据仅在单线程内访问,就不需要同步,这种技术称为"线程封闭",它是实现线程安全性最简单方式之一. ...
- 到底什么是哈希Hash?
有次面试被问到这个问题? 我说是经过运算的一串字符串,这个回答显然是让人不满意,连自己都不满意! 但是又对其很模糊,那么到底什么是Hash呢? 定义 Hash一般翻译为散列,还有音译为哈希,本文我们统 ...
- Java并发编程常识
这是why的第 85 篇原创文章 写中间件经常要做两件事: 1.延迟加载,在内存缓存已加载项. 2.统计调用次数,拦截并发量. 就这么个小功能,团队里的人十有八九写错. 上面这句话不是我说的,是梁飞在 ...