1.1、配置中心功能
集群中每一台主机的配置文件都是相同的,对配置文件的更新维护就成为了一个棘手的
问题,Nacos 是可以对 Spring Cloud 中各个微服务配置文件进行统一维护管理的配置中心。
1.2、常见配置中心工作原理
1.2.1 Spring Cloud Config
1.2.2 Nacos Config
(1) 系统架构
(2)数据模型
Namespace、Group、Service、DataId,DataId是配置文件的名称
在后面阅读 Nacos Config 源码过程中会看到一个概念:tenant。中文意思是租户,房客。 其实,tenant 就是 namespace,是 bootstrap.yml 文件属性 spring.cloud.nacos.config 中指定的 namespace。在代码中为了区分 spring.cloud.nacos.discovery 中指定的 namespace,所以起了 tenant 这个名字。
1.2.3 Apollo
Apollo 是由携程推出的一款开源的分布式配置中心。
(1) 系统架构
(2)工作原理
1.2.4 对比
1)、 系统架构复杂度:
Nacos Config 最为简单,无需消息总线系统,无需 Eureka 等。而 Apollo 与 Spring Cloud Config 系统搭建成本及复杂度较 Nacos 要高很多。
2)、羊群效应:
对于 Spring Cloud Config,Config Client 需要提交配置更新请求。当微服务系 统很庞大时,任何一个 Config Client 的更新请求的提交,都会引发所有“Bus 在线 Config Client“的配置更新请求的提交,即会引发羊群效应。这将会导致 Config Client 的效率下 降,导致整个系统的效率下降。而 Nacos Config 与 Apollo 则是“定点更新”,谁的配置 更新了向谁推送。
3)、自动感知配置更新:
Spring Cloud Config 是 Config Client 不提交请求,其是无法感知配置 更新的。但 Nacos 与 Apollo 则是,当 Config Server 中的配置文件发生了变更,Config Client 会自动感知到这个变更,无需 Config Client 端的用户做任何操作。
4)、配置文件类型:
Nacos Config 与 Spring Cloud Config 配置文件支持比较多的类型,支持 yaml、text、json、xml、html、Properties 等,但 Apollo 只支持 xml、text、Properties 类 型,不支持 yaml。
依赖
<dependencies>
<!--nacos discovery依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--nacos config依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--修改MySQL驱动版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
1.3、配置文件的加载
Nacos Config Client 在启动时是如何将远程配置中心 Nacos Config Server 中的配置文件加
载到本地的呢?这里要解决这个问题。
1.3.1 源码基础
(1) 基本配置说明
spring:
application:
#根据微服务名称来找动态配置文件
name: abcmsc-provider-depart
cloud:
nacos:
config:
server-addr: localhost:8848
#指定配置文件为yml类型
file-extension: yml
# 共享配置:要求共享配置文件与应用必须在同一个group中
shared-configs: aaa.yml, bbb.yml
# shared-configs[0]:
# data-id: aaa.yml
# refresh: true
# shared-configs[1]:
# data-id: bbb.yml
# refresh: true
# 扩展配置:当前应用与扩展配置文件无须在同一个group
extension-configs: ccc.yml, ddd.yml
# extension-configs[0]:
# data-id: ccc.yml
# group: other
# refresh: true
# extension-configs[1]:
# data-id: ddd.yml
# group: other
# refresh: true
#多环境选择
profiles:
active: test
# abcmsc-provider-depart-test.yml
nacos config client 要加载的配置文件有三种:
- 自身配置文件
- 共享配置文件
- 扩展配置文件
(2)共享配置说明
(3)扩展配置说明
(4)加载顺序说明
以上三类配置文件的加载顺序为,共享配置 -> 扩展配置 -> 当前应用自身配置,如果
存在相同属性配置了不同属性值,则后加载的会将先加载的给覆盖。即优先级为:共享配置
< 扩展配置 < 应用自身配置。
对于每种配置文件的加载过程,又存在三种可用选择:在应用本地存在同名配置,远程
配置中心存在同名配置,本地磁盘快照 snapshot 中存在同名配置。这三种配置的优先级为:
本地配置 > 远程配置 > 快照配置。只要前面的加载到了,后面的就不再加载。
若要在应用本地存放同名配置,则需要存放到当前用户主目录下的
nacos\config\fixed-localhost_8848_nacos\data\config-data\{groupId}目录中。
若开启了配置的快照功能,则默认会将快照记录在当前用户主目录下的
nacos\config\fixed-localhost_8848_nacos\snapshot\{groupId}目录中。
(5)、源码跟踪:Nacos Config Client配置文件的加载
// SpringBoot在启动时会准备环境,此时会调用该方法。
// 该方法会从配置中心加载配置文件
@Override
public PropertySource<?> locate(Environment env) {
// 将bootstrap.yml文件内容加载到内存
nacosConfigProperties.setEnvironment(env);
ConfigService configService = nacosConfigManager.getConfigService();
if (null == configService) {
log.warn("no instance of config service found, can't load config from nacos");
return null;
}
// 配置文件加载超时时限
long timeout = nacosConfigProperties.getTimeout();
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
timeout);
// 获取spring.cloud.nacos.config.name属性值,即要加载的配置文件的名称
String name = nacosConfigProperties.getName();
// 获取spring.cloud.nacos.config.prefix属性值,即要加载的配置文件的名称
String dataIdPrefix = nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
// 若没有设置name与prefix属性,则要加载的配置文件名称取spring.application.name属性值
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
// 加载共享配置
// 1)首先加载本地配置
// 2)若没有,则加载远程配置
// 3)若没有,则加载本地快照配置
loadSharedConfiguration(composite);
// 加载扩展配置
loadExtConfiguration(composite);
// 加载自身配置(注意,其会加载三类配置文件)
// 关于配置文件中的“三类”共有三种,要注意
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
1.4、配置文件的动态更新
当远程 Nacos Config Server 中的配置信息发生了变更,Nacos Config Client 是如何感知到
的呢?这里就来解决这个问题。
1.4.1 长轮询模型
Nacos Config Server 中配置数据的变更,Nacos Config Client 是如何知道的呢?Nacos
Config Server 采用了长轮询模型实现的变更通知。
一般情况下 Server 端数据的变更若要使 Client 感知到,可以选择两种模型:
Push 模型:当 Server 端的数据发生了变更,其会主动将更新推送给 Client。Push 模型
适合于 Client 数量不多,且 Server 端数据变化比较频繁的场景。其实时性较好,但其需
要维护长连接,占用系统资源。
Pull 模型:需要 Client 定时查看 Server 端数据是否更新。其实时性不好,且可能会产生
数据更新的丢失。
长轮询模型整合了 Push 与 Pull 模型的优势。Client 仍定时发起 Pull 请求,查看 Server
端数据是否更新。若发生了更新,则 Server 立即将更新数据以响应的形式发送给 Client 端;
若没有发生更新,Server 端不会发送任何信息,但其会临时性的保持住这个连接一段时间。
若在此时间段内,Server 端数据发生了变更,这个变更就会触发 Server 向 Client 发送变更结
果。这次发送的执行,就是因为长连接的存在。若此期间仍未发生变更,则放弃这个连接。
等待着下一次 Client 的 Pull 请求。
长轮询模型,是 Push 与 Pull 模型的整合,既减少了 Push 模型中长连接的被长时间维护
的时间,又降低了 Pull 模型实时性较差的问题。
1.4.2、 config client 定时发出更新检测
NacosConfigService -> ClientWorker –> 启动一个定时任务
cacheMap CacheData
1.4.3 、config client 将更新同步到应用实例
要解决的问题有两个:
1) config client 的每个配置文件对应的 cacheData 是什么时候创建的?
2) 如何将更新过的 cacheData 中的数据同步到应用实例的?
总思路:
在 config client 启动时会为每个其所需要的配置文件创建一个本地缓存 CacheData,并为
每个 CacheData 添加一个监听器。一旦监听到 CacheData 中数据发生了变更,就会引发监听
回调的执行。该回调并未直接从 CacheData 中读取变更数据,而是发布了一个刷新事件
RefreshEvent。该事件能够触发所有被@RefreshScope 注解的类实例被重新创建并初始化,而
初始化时使用的会自动更新的属性(被@Value 注解的属性)值就来自于 CacheData。
1.4.4 、config server 处理 client 配置变更检测请求
总思路:
当 config server 接收到 config client 的配置变更检测请求后,首先会解析出请求指定的
要检测的所有目标配置文件,同时也会解析出 client 对处理方式的要求。server 的处理方式
有四种类型:
短轮询:没有长连接维护。server 接收到 client 请求后立即轮询检测所有目标配置文件
是否发生变更,并将检测结果立即返回 client。不过,这个返回的结果与 nacos client 的
版本有密切关系,版本不同形成的结果不同。
固定时长的长轮询:server 接收到 client 请求后,会直接维护一个指定的固定时长的长
连接,默认 30s。长连接结束前会检测一次是否发生配置变更。不过,在长连接维护期
间是不检测配置变更情况的。
不挂起的非固定时长的长轮询:与短轮询类似。server 接收到 client 请求后立即轮询检
测所有目标配置文件是否发生变更,并将检测结果立即返回 client。与短轮询不同的是,
其返回结果与 nacos client 版本无关。
挂起的非固定时长的长轮询:server 接收到 client 请求后,会先检测是否发生了配置变
更。若发生了,则直接返回 client,关闭连接。若未发生,则首先会将这个长轮询实例
写入到一个缓存队列 allSubs,然后维护一个 30s 的长连接(这个时间用户不能指定)。
时间结束,长连接直接关闭。在该长连接维护期间,系统同时监听着配置变更事件,一
旦发生变更就会立即将变更发送给相应的长轮询对应的 client,关闭连接。
配置文件是否发生变更,是如何判断的?
就是把来自于 client 的配置文件的 md5 与 server 端配置文件的 md5 进行对比。若相等,则
未发生变更,否则,发生了变更。
server 创建的客户端长轮询实例,是为谁创建的?是为每个 config client 都会创建一个长轮
询实例?还是为每个配置文件都创建一个长轮询实例?
都不是。是为每次的配置更新检测请求创建一个长轮询实例。
1.4.5、config server 感知配置变更后通知 client
总思路:
在 Nacos Config Server 启动时会创建 LongPollingService 实例,而在创建 LongPollingService
实例时,会首先创建一个 allSubs 队列,同时还会注册一个 LocalDataChangeEvent 的订阅者。
一旦 Server 中保存的配置发生变更,就会触发订阅者的回调方法的执行。而回调方法则会
引发 DataChangeTask 的异步任务执行。
DataChangeTask 任务就是从当前 server 所 hold 的所有长轮询实例集合 allSubs 中查找,
这个发生了变更的配置文件是哪个长轮询实例对应的 client 要检测的变更,然后将这个发生
变更的配置文件key发送给这个长轮询对应的client,并将这个长轮询实例从allSubs中删除。