Nacos配置中心
# 1 核心链路图
# 2 阅读备忘
# 2.1 核心流程理解
客户端功能描述
客户端服务启动阶段,准备
上下文
时,会根据dataId
遍历调用Nacos服务端接口/v1/cs/configs
GET方式
获取配置文件内容并写到本地磁盘,如果本地磁盘已经存在相应配置文件,则优先使用本地配置,不调用接口。客户端服务启动阶段,通过
spring.factories
的方式去加载Nacos相关自动配置类,这些配置类最终执行的动作是:
- 定时调用Nacos服务端接口
/v1/cs/configs/listener
POST方式
来获取有哪些配置文件发生变更,并获得相应配置文件的标识。- 根据这些标识再去调用Nacos服务端接口
/v1/cs/configs
GET方式
获取配置文件内容。- 通过校验
MD5
的方式判断是否跟客户端本地的文件不一致,不一致则去刷新spring的Environment
配置数据,并重新加载带@RefreshScope
相关的Bean。
服务端功能描述
Nacos数据源配置为mysql
spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos141?characterEncoding=utf8 db.user.0=root db.password.0=123456
服务启动后会去mysql查询配置信息并存储到磁盘。
接口
/v1/cs/configs
GET方式
会去查询本地磁盘,把获取到的配置文件内容返回给客户端。接口
/v1/cs/configs/listener
POST方式
会通过长轮询的方式去比较客户端配置文件的MD5与服务端配置文件的MD5是否一致,返回不一致的数据。管理界面发布配置会往数据库新增信息,并发布数据变更事件,通知集群其他节点同步配置。
# 2.2 刷新Environment对象详解
public synchronized Set<String> refreshEnvironment() {
// 获取刷新配置前的配置信息,对比用
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
// 刷新Environment
addConfigFilesToEnvironment();
// 这里上下文的Environment已经是新的值了
// 进行新旧对比,结果返回有变化的值
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
2
3
4
5
6
7
8
9
10
11
12
13
重点在addConfigFilesToEnvironment方法,刷新Environment:
ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
// 从上下文拿出Environment对象,copy一份
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
// SpringBoot启动类builder,准备新做一个Spring上下文启动
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
// banner和web都关闭,因为只是想单纯利用新的Spring上下文构造一个新的Environment
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
// 传入我们刚刚copy的Environment实例
.environment(environment);
// 启动上下文
capture = builder.run();
// 这个时候,通过上下文SpringIOC的启动,刚刚Environment对象就变成带有最新配置值的Environment了
// 获取旧的外部化配置列表
MutablePropertySources target = this.context.getEnvironment()
.getPropertySources();
String targetName = null;
// 遍历这个最新的Environment外部化配置列表
for (PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (target.contains(name)) {
targetName = name;
}
// 某些配置源不做替换,读者自行查看源码
// 一般的配置源都会进入if语句
if (!this.standardSources.contains(name)) {
if (target.contains(name)) {
// 用新的配置替换旧的配置
target.replace(name, source);
} else {
//....
}
}
}
}
}
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
新做了一个Spring上下文,因为Spring启动后会对上下文中的Environment进行初始化,获取最新配置,所以这里利用Spring的启动,达到了获取最新的Environment对象的目的。然后去替换旧的上下文中的Environment对象中的配置值。
# 2.3 长轮询详解

接收到客户端的请求,将请求封装成
clientLongPolling
任务提交到延时调度线程池,线程池延时时间为29.5s,同时将这个clientLongPolling任务添加到一个allSubs集合中。在29.5秒这段期间内,没有数据变更,则29.5s后开始执行
clientLongPolling
任务,首先将clientLongPolling
任务从allSubs
中移除,再检查客户端的MD5和服务端的MD5值是否一致,将不一致的返回。29.5s内服务端接收到数据更新操作,发布一个异步通知,就是将这个请求添加到一个阻塞队列,有一个线程从这个队列中取数据消费,消费逻辑是比对客户端和服务端的MD5是否一致,然后将比对结果返回,最后从
allSub
中取出相对应的clientLongPolling
任务,将这个任务取消,这样延时队列到期将不会做任何操作。