文章目录
前言
最近几天在看Nacos相关内容,它同时拥有注册中心和配置中心的功能,比Spring Cloud Config和 Spring Cloud Eureka好用很多,在使用的过程中(目前仅使用配置中心相关功能),自己也产生过几个疑问(其实就是挖了下实现原理)并找到了答案,在此分享给大家。
Spring Cloud Config和 Spring Cloud Eureka问题:
- Eureka的停止更新。
- Config 配置修改刷新时需要SpringCloud Bus消息总线发出消息通知(Kafka、RabbitMQ等)到各个服务完成配置动态更新。
- 没有相应的可视化控制台。
nacos可以同时实现注册和配置中心,以及配置的动态更新。
Nacos不清楚的可以参考我的csdn文章,太基础的放在公众号不合适,浪费大家的时间
由于篇幅问题,本文先介绍第一个疑问Nacos如何加载远程配置(拉取远程配置的原理)并给到Spring环境使用?
Nacos如何加载远程配置?
远端配置如下:
物理架构如下:
启动Nacos Client和Server程序,用wireshark去抓包分析
wireshark抓包分析
设置过滤http协议
和目标ip为Nacos Server的地址
抓包结果如下,全部的请求都在这儿了。
追踪/nacos/v1/cs/configs?dataId=laker-dev.yaml...
结果如下:
GET /nacos/v1/cs/configs?dataId=laker-dev.yaml&group=DEFAULT_GROUP HTTP/1.1
Content-Type: application/json
Accept-Charset: UTF-8
Accept-Encoding: gzip
Content-Encoding: gzip
Client-AppName: unknown
Client-RequestTS: 1610697169595
Client-RequestToken: 0ae60c7db35f4bc668e25c76238a8c2b
Client-Version: 1.3.2
exConfigInfo: true
RequestId: c2bb905c-7454-4933-8a36-26f6068a8261
User-Agent: Java/1.8.0_102
Host: 10.7.11.13:8848
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
HTTP/1.1 200
Config-Type: yaml
Content-MD5: 508ca88692b12ccd3321073447ec8ea3
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache,no-store
Last-Modified: Fri, 15 Jan 2021 06:41:27 GMT
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Jan 2021 07:52:49 GMT
Keep-Alive: timeout=60
Connection: keep-alive
laker:
name: laker1222222
重点:远端返回的内容=我们本地在application.yml配置的内容一样
laker:
name: laker1222222
原理分析
核心就是spring-cloud-context
中的配置扩展接口PropertySourceLocator
,Nacos 实现的类为NacosPropertySourceLocator
。
1. 当spring程序启动时会调用locate方法。
public PropertySource<?> locate(Environment env) {
// 1. 创建一个跟远程打交道的对象NacosConfigService
ConfigService configService = nacosConfigProperties.configServiceInstance();
... 省略代码
// 2. 操作NacosPropertySource对象,下面三个方法最终都会调用该对象build
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
// 3.
String name = nacosConfigProperties.getName();
String dataIdPrefix = nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
// 从远程获取的properties会存放到该类,最终放到Environment中
CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);
// 加载公共模块配置
loadSharedConfiguration(composite);
// 加载扩展配置
loadExtConfiguration(composite);
// 加载独有配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
2. debug跟踪,真正获取远程配置的是 NacosConfigService调用getConfigInner()。
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
... 省略代码
// 1. 优先使用failvoer配置
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
... 省略代码
try {
// 2. 服务器获取配置
content = worker.getServerConfig(dataId, group, tenant, timeoutMs);
... 省略代码
return content;
} catch (NacosException ioe) {
if (NacosException.NO_RIGHT == ioe.getErrCode()) {
throw ioe;
}
}
// 3. 当服务器挂了就拿本地快照
content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
... 省略代码
return content;
}
--- 内容为
laker:
name: laker1222222
当服务器挂了就拿本地快照
,这个机制挺好的,本地快照位置如下图:
内容如下:
laker:
name: laker1222222
3. 解析远端拿回的内容,NacosDataParserHandler. parseNacosData
支持格式如下:
- Json
- Properties
- XML
- Yaml
解析为MapPropertySource
供Spring环境使用。
重复造轮子
准备
分析了一波,实现好像也很简单吗,我们自己来重复造轮子实现一个读取本地Json文件
Json内容如下:
{
"laker.name":"lakerttt"
}
Client的bootstrap.yml:
laker:
file: D://laker.json
1. 实现自定义PropertySourceLocator
public class LakerPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment environment) {
String fileName = environment.getProperty("laker.file");
FileReader reader = new FileReader(new File(fileName));
String json = reader.readString();
Map<String, Object> result = new LinkedHashMap<>(32);
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> dataMap = mapper.readValue(json, LinkedHashMap.class);
return new MapPropertySource("laker", dataMap);
}
}
PropertySourceLocator 扩展接口,我们可以在该接口实现自定义配置的加载,比如从数据库中获取配置,或者文件中获取配置等。
配置可以是json、xml、yaml等等
2. 启用自定义PropertySourceLocator
@Configuration
public class LakerConfigConfiguration {
@Bean
public LakerPropertySourceLocator greizPropertySourceLocator() {
return new LakerPropertySourceLocator();
}
}
在resources的META-INF/spring.factories
添加启动指定加载类
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.laker.config.LakerConfigConfiguration
- BootstrapConfiguration简介
Spring.factories中的配置项, org.springframework.cloud.bootstrap.BootstrapConfiguration,会在Spring启动之前的准备上下文阶段将其加入到容器中。BootstrapConfiguration是在刷新(refresh)阶段将class作为预选的配置类@Configuration注入到容器中的,在@Configuration配置解析阶段会解析此class。
扩展
1. 配置文件 bootstrap和application区别
Spring Cloud 的官方文档解释如下:
https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#_the_bootstrap_application_context
Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap, 另外一种是 application, bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。
因此,对比 application 配置文件,bootstrap 配置文件具有以下几个特性。
- boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载
- boostrap 里面的属性不能被覆盖
bootstrap/ application 的应用场景
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 配置文件有以下几个应用场景。
- 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
- 一些固定的不能被覆盖的属性
- 一些加密/解密的场景;
2. 加载位置与顺序
SpringBoot启动会扫描以下位置的application.properties/yml文件作为spring boot的默认配置文件:
file:./config/
file:./
classpath:/config/
classpath:/
以上顺序按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级的配置内容会覆盖低优先级配置的内容,其中配置文件中的内容是互补配置,即
- 存在相同的配置内容,高优先级的内容会覆盖低优先级的内容
- 存在不同的内容的时候,高优先级和低优先级的配置内容取并集
3. 外部配置的加载顺序
Spring Boot也可以从以下位置加载配置: 优先级从高到低顺序,高优先级覆盖低优先级,如有不同内容,高优先级和低优先级形成互补配置
- 命令行参数
命令行参数的优先级是最高的,假定内部配置的最高优先级配置文件配置的启动端口号是8081,启动命令行参数如以下设置:
java -jar spring-boot-02-config-02.0.0.1-SNAPSHOT.jar --server.port=8089
那么启动的端口就改成了8089,命令行可以把项目的所有的配置选项全部都改掉
-
来自java:comp/env的JNDI属性
-
java系统属性(System.getProperties())
-
操作系统环境变量
-
RandomValuePropertySource配置的
random.\*
属性值 -
jar包外部的
application-{profile}.properties
或者application.yml(带spring.profile)
配置文件 -
jar包内部的
application-{profile}.properties
或者application.yml(带spring.profile)
配置文件 -
jar包外部的
application.properties
或者application.yml(不带spring.profile)
配置文件 -
jar包内部的
application.properties
或者application.yml(不带spring.profile)
配置文件 -
@Configuration注解类上的@PropertySource
-
通过SpringApplication.setDefaultProperties指定的默认属性
参考:
- https://www.cnblogs.com/wolf-bin/p/11532708.html
本系列目录
🍎QQ群【837324215】
🍎关注我的公众号【Java大厂面试官】,一起学习呗🍎🍎🍎