一、前言
项目上使用Nacos做为配置中心,以前使用的是Concul;现在准备详细梳理一下Nacos做为配置中心的配置文件加载优先级问题,因为以前在使用Consul的时候,由于配置文件优先级问题踩过大坑。
二、实战
1、集成Nacos配置中心
1) POM中引入SpringCloud、Spring Cloud Alibaba依赖,记得做好maven版本管理、否则存在版本问题一地鸡毛的场景。
<properties>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!--整合nacos config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2) 在bootstrap.yml中配置Nacos-server的地址
server:
port: 9001
spring:
application:
name: config1 # 配置名,即Nacos dashboard中的Data id
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 # 配置中心地址,集群的话多个节点使用,分隔
file-extension: yaml # 配置的后缀名
namespace: 2c38da96-f654-4105-bbc0-63befaa449f0 # 命名空间ID
group: DEFAULT_GROUP # 指定要获取配置的组
3) 随便写个Controller,用@Value
等注解或ConfigurableApplicationContext
获取配置信息。
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author zhouxin
* @RefreshScope 动态加载配置
**/
@RefreshScope
@RestController
public class NacosServerController {
@Resource
private ConfigurableApplicationContext configurableApplicationContext;
/**
* 可用于验证本服务配置文件和extension-configs之间的优先级
*/
@Value("${student.name:null}")
private String name;
/**
* 如果只是通过@Value获得的配置信息,不会随着Nacos的修改操作而获得最新配置信息。那么,实时获取最新配置信息有两种方式:
* 方式1:添加@RefreshScope注解。
* 方式2:通过getProperty的方式,获得最新的配置信息。
*
* @return
*/
@GetMapping("/name")
public String getName() {
String name1 = configurableApplicationContext.getEnvironment().getProperty("student.name");
return String.format("name=%s <br> name1=%s", name, name1);
}
Nacos服务端config1.yaml配置如下:
启动服务,通过HTTP访问(http://localhost:9001/name)到的结果如下:
这说明:我们可以通过很多中方式注入配置,包括:@Value、@ConfigurationProperties、ConfigurableApplicationContext等。
2、应用配置共享
Nacos提供两种应用间共享配置的方式:扩展DataId(extension-configs
)、共享DataId(shared-configs
);另外需共享的DataId,yaml后缀不能少
,且目前只支持yaml/properties。
0)Nacos服务端配置准备
(1)config1.yaml
(2)config2.yaml
(3)config3.yaml
(4)config4.yaml
(5)config5.yaml
1)扩展Data Id配置
Spring Cloud Alibaba Nacos Config 从 0.2.1 版本后,可支持自定义扩展Data Id 配置,特性如下:
1、支持多个 Data Id 的配置;
通过spring.cloud.nacos.config.extension-configs[n].data-id
的配置方式2、可以自定义Data Id 所在的组,不明确配置的话,默认是 DEFAULT_GROUP;
通过spring.cloud.nacos.config.extension-configs[n].group
的配置方式3、当某个Data Id 的配置变更时,自定义应用中是否
动态刷新配置值
,默认为否;通过spring.cloud.nacos.config.extension-configs[n].refresh
的配置方式
源码中的体现:
(1)在bootstrap.yml文件中按住command 鼠标左键点extension-configs
,进入如下源码:
从上面我们可以看出扩展DataID的主体是Config类,我们看一下它:
从这里我们便能看出上面提到的extension-configs
的特性和一些默认值。
使用:
(1)bootstrap.yml文件中增加如下配置:
spring:
cloud:
nacos:
config:
# 使用extension-configs[n],配置加载多个dataId
extension-configs:
- data-id: config4.yaml
group: CONFIG4_GROUP
refresh: true # 动态刷新配置
- data-id: config5.yaml
group: DEFAULT_GROUP
refresh: true # 动态刷新配置
(2)controller中引入只存在于config4.yaml和config5.yaml中的配置:
@Value("${config4.name:null}")
private String config4Name;
@Value("${config5.name:null}")
private String config5Name;
@GetMapping("/allname")
public String getAllName() {
return String.format("config4Name=%s <br> config5Name=%s", config4Name, config5Name);
}
通过HTTP访问(http://localhost:9001/allname)到的结果如下:
2)共享Data Id配置
为了更加清晰的在多个应用间配置共享的 Data Id ,也可以通过以下的方式来配置:
1、支持多个 Data Id 的配置;
通过spring.cloud.nacos.config.shared-configs[n].data-id
的配置方式2、可以自定义Data Id 所在的组,不明确配置的话,默认是 DEFAULT_GROUP;
通过spring.cloud.nacos.config.shared-configs[n].group
的配置方式3、当某个Data Id 的配置变更时,自定义应用中是否
动态刷新配置值
,默认为否;通过spring.cloud.nacos.config.shared-configs[n].refresh
的配置方式
使用:
(1)bootstrap.yml文件中增加如下配置:
spring:
cloud:
nacos:
config:
# 使用shared-config[n],配置加载多个dataId
shared-configs:
- data-id: config2.yaml
group: DEFAULT_GROUP
refresh: true # 动态刷新配置
- data-id: config3.yaml
group: DEFAULT_GROUP
refresh: true # 动态刷新配置
(2)controller中引入只存在于config2.yaml和config3.yaml中的配置:
@Value("${config2.name:null}")
private String config2Name;
@Value("${config3.name:null}")
private String config3Name;
@GetMapping("/allname")
public String getAllName() {
return String.format("config2Name=%s <br> config3Name=%s", config2Name, config3Name);
}
通过HTTP访问(http://localhost:9001/allname)到的结果如下:
聪明的大家肯定发现这和extension-configs扩展DataId的方式一毛一样啊,是的,它就一毛一样。
但是注意,在老版本中它俩的扩展方式还真不一样,下面我们来看一下新老版本扩展/共享DataId的差异?
3、扩展/共享DataId新老版本差异
1)老版本的pom
<properties>
<java.version>1.8</java.version>
<spring.boot>2.1.3.RELEASE</spring.boot>
<spring.cloud>Greenwich.RELEASE</spring.cloud>
<spring.cloud.alibaba>2.1.0.RELEASE</spring.cloud.alibaba>
</properties>
<dependencyManagement>
<dependencies>
<!-- 引入Spring Cloud Alibaba依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 引入Spring Cloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 引入Spring Boot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2)扩展DataId方式的差异
新版本yaml中使用extension-configs
,老版本yaml使用ext-config
;除此之外,功能上并没有区别。
3)分享DataId方式的差异
(1)使用上:
1、新版本yaml中使用shared-configs
;
2、老版本yaml使用shared-dataids
根据DataID加载配置信息、使用refreshable-dataids
实现指定DataId配置信息的动态刷新;
2)功能上:
新版本yaml中支持指定group,老版本中不支持指定Group,只会取默认的group--
DEFAULT_GROUP
;
为什么老版本不支持指定group呢?我们来看一下:
3)源码分析
(1) 在bootstrap.yml文件中按住command 鼠标左键点shared-dataids
,进入如下源码:
public static final String SEPARATOR = "[,]";
这里我们可以看到它会将传入的sharedDataids以,分隔为多个DataId,并将DataId作为入参构造Config:
其中只会指定dataId,并不会牵扯到group。
(2) 我们再看shared-dataids
:
它也只是将拆分出的所有dataId对应的Config的refresh属性设置为true,也不牵扯到group。
(3)会不会有group-dataids呢?
进入NacosConfigProperties
类中全局搜索一把,并没有。
好了,新老版本的差异我们也聊完了,下面看一下配置文件加载的优先级吧。
4、扩展的配置文件加载优先级
Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置;
A: 通过 spring.cloud.nacos.config.shared-configs[n].data-id 支持多个共享 Data Id 的配置;
B: 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的方式支持多个扩展 Data Id 的配置;
C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置;
当三种方式共同使用时,
总的来说 他们的一个优先级关系是:A < B < C
。
(1)以老版本来说:
1、shared-dataids中越靠后,优先级越高;
2、ext-config中n越大、或越靠后,优先级越高;
3、Shared-dataids < ext-config < 内部;
(2)以新版本来说:
1、shared-configs中n越大、或越靠后,优先级越高;
2、extension-configs中n越大、或越靠后,优先级越高;
3、shared-configs < extension-configs < 内部;
1)逻辑图
相信大家看了这个图,也不需要做过多解释了。
2)代码
(1)controller:
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author zhouxin
* @RefreshScope 动态加载配置
**/
@RefreshScope
@RestController
public class NacosServerController {
@Resource
private ConfigurableApplicationContext configurableApplicationContext;
/**
* 可用于验证本服务配置文件和extension-configs之间的优先级
*/
@Value("${student.name:null}")
private String name;
@Value("${config2.name:null}")
private String config2Name;
@Value("${config3.name:null}")
private String config3Name;
@Value("${config4.name:null}")
private String config4Name;
@Value("${config5.name:null}")
private String config5Name;
/**
* 用于验证shared-configs之间的优先级
*/
@Value("${config.name:null}")
private String configName;
/**
* extension-configs和shared-configs的优先级
*/
@Value("${ext.name:null}")
private String extName;
/**
* 用于验证extension-configs之间的优先级
*/
@Value("${ext.gender:null}")
private String extGender;
/**
* 如果只是通过@Value获得的配置信息,不会随着Nacos的修改操作而获得最新配置信息。那么,实时获取最新配置信息有两种方式:
* 方式1:添加@RefreshScope注解。
* 方式2:通过getProperty的方式,获得最新的配置信息。
*
* @return
*/
@GetMapping("/name")
public String getName() {
String name1 = configurableApplicationContext.getEnvironment().getProperty("student.name");
return String.format("name=%s <br> name1=%s", name, name1);
}
@GetMapping("/allname")
public String getAllName() {
return String.format("config1Name=%s<br> config2Name=%s<br> config3Name=%s<br> config4Name=%s <br> config5Name=%s <br> configName=%s <br> extName=%s <br> extGender=%s", name,
config2Name, config3Name, config4Name, config5Name, configName, extName, extGender);
}
}
(2)新版本bootstrap.yml配置:
server:
port: 9001
spring:
application:
name: config1
cloud:
nacos:
config:
# 此处的nacos用于配置管理
server-addr: 127.0.0.1:8848
file-extension: yaml
namespace: 2c38da96-f654-4105-bbc0-63befaa449f0 # 命名空间ID
group: DEFAULT_GROUP # 指定要获取配置的组
shared-configs:
- data-id: config2.yaml
refresh: true
- data-id: config3.yaml
refresh: true
extension-configs:
- data-id: config4.yaml
group: CONFIG4_GROUP
refresh: true # 动态刷新配置
- data-id: config5.yaml
group: DEFAULT_GROUP
refresh: true # 动态刷新配置
(3)老版本bootstrap.yml配置:
server:
port: 9001
spring:
application:
name: config1 # 配置名
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 # 配置中心地址——单机
file-extension: yaml # 后缀名
namespace: 2c38da96-f654-4105-bbc0-63befaa449f0 # 命名空间ID
group: DEFAULT_GROUP # 指定要获取配置的组
# 使用shared-dataids,配置加载多个dataId
shared-dataids: config2.yaml,config3.yaml
refreshable-dataids: config2.yaml
# 使用ext-config[n],配置加载多个dataId
ext-config:
- data-id: config4.yaml
group: CONFIG4_GROUP
refresh: true # 动态刷新配置
- data-id: config5.yaml
group: DEFAULT_GROUP
refresh: true # 动态刷新配置
5、本地和远程配置优先级
SpringCloud中,nacos是借助SpringCloud的Config来加载属性源的,所以是否覆盖系统属性和配置文件属性的设置也是通过SpringCloud的配置进行触发。
默认情况下nacos属性源配置优先级最高,会覆盖系统属性源和配置属性源(本地文件)。
可以通过在远程配置中心(Nacos服务中)中做如下配置,设置本地配置覆盖远程配置:
spring:
cloud:
config:
# Nacos远程配置是否不覆盖其他属性源(文件、系统),默认为false,即覆盖其他源(文件、系统),当allow-override:为true时才会生效
override-none: true
# 是否允许Nacos远程配置被本地文件覆盖,默认为true
allow-override: true
# Nacos远程配置是否可以覆盖系统属性源(系统环境变量或系统属性),默认为true,即允许
override-system-properties: false
注意:
1、将其放在bootstrap.yml或application.yml配置文件中是无效的。
2、如果allow-override值为false,即使将override-none设置为true,也是无效的。
6、其他
通过设置 spring.cloud.nacos.config.enabled = false
来完全关闭 Spring Cloud Nacos Config;
更多Nacos-config的内容请参考Nacos的GitHub Wiki:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config
三、最佳实践
仁者见仁智者见智,仅供参考;
1、能放本地、不放远程;避免滥用远程服务器;
2、尽量规避优先级;
3、定规则,例如所有配置属性都要加上注释;
4、配置管理人员尽量少;
最后,虽然我用的Nacos1.4.1版本,但是不建议大家在生产环境使用这个版本,因为该版本存在一个致命的bug:
客户端与 Nacos Server 如果发生短暂的域名解析问题,会导致心跳永久丢失,进而引发服务全量下线,即使网络恢复,也不会自动恢复心跳。
域名解析失败常见于网络抖动或者 K8s 环境下的 coreDNS 访问超时等场景,并且该问题仅存在于 1.4.1 版本,低于此版本不受此问题的影响,使用 1.4.1 的用户建议升级至 1.4.2 以避免此问题(源自阿里内部博文)。