分布式配置
# 分布式配置
# 1、简介
Nacos 提供用于存储配置和其他元数据的 key/value 存储,为分布式系统中的外部化配置提供服务器端和客户端支持。
Spring Cloud Alibaba Nacos Config 是 Config Server 和 Client 的替代方案,在特殊的 bootstrap 阶段,配置被加载到 Spring 环境中。
# 2、学习目标
- 使用 Nacos Config 作为 Spring Cloud 分布式配置
- 使用 Nacos Config 实现 Bean 动态刷新
- 了解 Nacos Config 高级配置
# 3、详细内容
- 快速上手:使用 Nacos Config 作为外部化配置源
- 多文件扩展名支持:以
YAML
文件扩展名为例,讨论 Nacos Config 多文件扩展名支持 - 动态配置更新:演示
@RefreshScope
特性,实现 Bean 动态刷新 - 自定义扩展:自定义 namespace、Group 以及 Data Id 的配置扩展
- 运维特性:演示 Nacos Config 高级外部化配置以及 Endpoint 内部细节
# 4、快速上手
# 引入 Nacos Config 的两种方式
Nacos Config 引入的方式同样也有两种,即 Aliyun Java Initializr(云原生应用脚手架 (opens new window))引入和 Maven pom.xml 依赖。
官方推荐使用 Aliyun Java Initializr 方式引入 Nacos Discovery,以便简化组件之间的依赖关系。
# 快速上手
以脚手架引入为例
选择三个组件:
生成的相应依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2
3
4
版本号问题
不过该 starter 并未指定版本,具体的版本声明在 com.alibaba.cloud:springcloud-alibaba-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>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
它们的版本定义在 <properties>
元素中,即 2.2.1.RELEASE
和 2.3.0.RELEASE
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
2
3
4
5
6
7
# 启动 Nacos 服务器
阿里为开发者提供了一套免费的 Nacos Server :进入 http://139.196.203.133:8848/nacos/ (opens new window) 查看控制台(账号名/密码为 nacos-configuration/nacos-configuration)。
具体启动方式参考 Nacos 快速开始 (opens new window)。
操作流程官方示例:
选择 "配置管理/配置列表"
由于服务是公共免费的,为了做好隔离,所以分布式配置的功能,请选择在 sandbox -configuration 的命名空间下操作
Data ID 由应用名(
nacos-config-sample
)+ 文件后缀名(.properties
) 组成,配置内容:user.name=nacos-config-sampleuser.age=90
1应用名:对应
spring.application.name=xxx
中的 xxx 部分回到应用 nacos-config-sample 工程,在 resources 目录下新建名为 “
application.properties
" 文件,并配置以下内容:spring.cloud.nacos.config.server-addr=139.196.203.133:8848 spring.cloud.nacos.config.username=nacos-configuration spring.cloud.nacos.config.password=nacos-configuration spring.cloud.nacos.config.namespace=sandbox-configuration
1
2
3
4读取 Nacos Config 实现
@SpringBootApplicationpublic class NacosConfigSampleApplication { @Value("${user.name}") private String userName; @Value("${user.age}") private int userAge; @PostConstruct public void init() { System.out.printf("[init] user name : %s , age : %d%n", userName, userAge); } public static void main(String[] args) { SpringApplication.run(NacosConfigSampleApplication.class, args); } }
1
2
3
4
5
6
7
8
9
10
11
12
13启动 Nacos Config 应用
运行 nacos-config-sample 引导类
NacosConfigSampleApplication
,观察控制台结果(截取关键日志信息):[init] user name : nacos-config-sample , age : 9
1
# 5、使用 Nacos Config 实现 Bean 动态刷新
Nacos Config 支持标准 Spring Cloud @RefreshScope
特性,即应用订阅某个 Nacos 配置后,当配置内容变化时,Refresh Scope Beans 中的绑定配置的属性将有条件的更新。
所谓的条件是指 Bean 必须:
- 必须条件:Bean 的声明类必须标注
@RefreshScope
- 二选一条件:
- 属性(非 static 字段)标注
@Value
@ConfigurationProperties Bean
- 属性(非 static 字段)标注
除此之外,Nacos Config 也引入了 Nacos Client 底层数据变化监听接口, 即 com.alibaba.nacos.api.config.listener.Listener
。
Nacos Client:Nacos 客户端 API,也是 Nacos Config 底层依赖
# @Value 场景分析
代码实现
@SpringBootApplication
@RestController
@RefreshScopepublic
class NacosConfigSampleApplication {
@Value("${user.name}")
private String userName;
@Value("${user.age}")
private int userAge;
@PostConstruct
public void init() {
System.out.printf("[init] user name : %s , age : %d%n", userName, userAge);
}
@PreDestroy
public void destroy() {
System.out.printf("[destroy] user name : %s , age : %d%n", userName, userAge);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
当 Nacos Config 接收到服务端配置变更时,对应的 @RefreshScope Bean
生命周期回调方法会被调用,并且是【先销毁】,然后又【重新初始化】。
要意识到 NacosConfig 配置变更对 @RefreshScope Bean
生命周期回调方法的影响,避免出现重复初始化等操作。
注: Nacos Config 配置变更调用了 Spring Cloud API
ContextRefresher
,该 API 会执行以上行为。同理,执行 Spring Cloud Acutator Endpointrefresh
也会使用ContextRefresher
。
# @ConfigurationProperties Bean 的场景分析
1、新增 User 类
@RefreshScope
@ConfigurationProperties(prefix = "user")
public class User implements InitializingBean, DisposableBean {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
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
2、修改 NacosConfigSampleApplication 代码
@SpringBootApplication
@RestController
@RefreshScope
@EnableConfigurationProperties(User.class)
public class NacosConfigSampleApplication {
@Value("${user.name}")
private String userName;
@Value("${user.age}")
private int userAge;
@Autowired
private User user;
@PostConstruct
public void init() {
System.out.printf("[init] user name : %s , age : %d%n", userName, userAge);
}
@PreDestroy
public void destroy() {
System.out.printf("[destroy] user name : %s , age : %d%n", userName, userAge);
}
@RequestMapping("/user")
public String user() {
return "[HTTP] " + user;
}
public static void main(String[] args) {
SpringApplication.run(NacosConfigSampleApplication.class, args);
}
}
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
主要改点:
- 激活 @ConfigurationProperties Bean
@EnableConfigurationProperties(User.class)
。 - 通过
@Autowired
依赖注入 User Bean。 - 使用 user Bean 的
toString()
方法替换user()
中的实现
# Nacos Config 监听实现 Bean 属性动态刷新
代码实现
@Configuration
@EnableConfigurationProperties(User.class)
public class NacosConfigDemoConfiguration {
@Autowired
private NacosConfigManager nacosConfigManager;
@Autowired
private User user;
@Value("${user.name}")
private String userName;
@Value("${user.age}")
private int userAge;
@Bean
public ApplicationRunner runner() {
return args -> {
String dataId = "nacos-config-sample.properties";
String group = "DEFAULT_GROUP";
nacosConfigManager.getConfigService().addListener(dataId, group, new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("[Listener] " + configInfo);
System.out.println("[Before User] " + user);
Properties properties = new Properties();
try {
properties.load(new StringReader(configInfo));
String name = properties.getProperty("user.name");
int age = Integer.valueOf(properties.getProperty("user.age"));
user.setName(name);
user.setAge(age);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("[After User] " + user);
}
});
};
}
@PostConstruct
public void init() {
System.out.printf("[init] user name : %s , age : %d%n", userName, userAge);
}
@PreDestroy
public void destroy() {
System.out.printf("[destroy] user name : %s , age : %d%n", userName, userAge);
}
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
代码主要变化:
@Autowired
依赖注入NacosConfigManager
。新增
runner()
方法,通过NacosConfigManagerBean
获取ConfigService
,并增加了AbstractListener
( Listener 抽象类)实现,监听
dataId = "nacos-configsample.properties"
和group = "DEFAULT_GROUP"
重启应用,并将配置 user.age 从 90 调整到 19,观察日志变化:
[Listener] user.name=nacos-config-sampleuser.age= 19
[Before User] User{name='nacos-config-sample', age=90}
[After User] User{name='nacos-config-sample', age=19}
2
3
# 总结
上述三个例子均围绕着 Nacos Config 实现 Bean 属性动态更新,不过它们是 Spring Cloud 使用场景。
如果读者的应用仅使用 Spring 或者 Spring Boot,可以考虑 Nacos Spring 工程,Github 地址:https://github.com/nacos-group/nacos-spring-project (opens new window),其中 @NacosValue
支持属性粒度的更新。
# 6、Nacos Config 高级配置
# 支持自定义 namespace 的配置
例如:
spring.cloud.nacos.config.namespace=b3404bc0-d7dc-4855-b519-570ed34b62d7
注:该配置必须放在 bootstrap.properties 文件中
# 支持自定义 Group 的配置
例如:
spring.cloud.nacos.config.group=DEVELOP_GROUP
# 支持自定义扩展的 Data Id 配置
一个完整的配置案例如下所示:
spring.application.name=opensource-service-provider
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
# config external configuration
# 1、Data Id 在默认的组 DEFAULT_GROUP,不支持配置的动态刷新
spring.cloud.nacos.config.extension-configs[0].data-id=ext-config-common01.properties
# 2、Data Id 不在默认的组,不支持动态刷新
spring.cloud.nacos.config.extension-configs[1].data-id=ext-config-common02.properties
spring.cloud.nacos.config.extension-configs[1].group=GLOBALE_GROUP
# 3、Data Id 既不在默认的组,也支持动态刷新
spring.cloud.nacos.config.extension-configs[2].data-id=ext-config-common03.properties
spring.cloud.nacos.config.extension-configs[2].group=REFRESH_GROUP
spring.cloud.nacos.config.extension-configs[2].refresh=true
2
3
4
5
6
7
8
9
10
11
12
可以看到:
- 通过
spring.cloud.nacos.config.extension-configs[n].data-id
的配置方式来支持多个 Data Id 的配置。 - 通过
spring.cloud.nacos.config.extension-configs[n].group
的配置方式自定义 Data Id 所在的组,不明确配置的话,默认是 DEFAULT_GROUP。 - 通过
spring.cloud.nacos.config.extension-configs[n].refresh
的配置方式来控制该 Data Id 在配置变更时,是否支持应用中可动态刷新,感知到最新的配置值。默认是不支持的。
需要注意的点:
- 多个 Data Id 同时配置时,他的优先级关系是
spring.cloud.nacos.config.extension-configs[n].data-id
其中 n 的值越大,优先级越高。 spring.cloud.nacos.config.extension-configs[n].data-id
的值必须带文件扩展名,文件扩展名既可支持 properties,又可以支持 yaml/yml。
清晰案例
# 配置支持共享的 Data Id
spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml
# 配置 Data Id 所在分组,缺省默认 DEFAULT_GROUP
spring.cloud.nacos.config.shared-configs[0].group=GROUP_APP1
# 配置 Data Id 在配置变更时,是否动态刷新,缺省默认 false
spring.cloud.nacos.config.shared-configs[0].refresh=true
2
3
4
5
6
# 配置的优先级
目前提供了三种配置能力从 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
# 完全关闭配置
可以通过设置 spring.cloud.nacos.config.enabled = false
来完全关闭 Spring Cloud Nacos Config
# 7、Nacos Config Actuator Endpoint
Nacos Config 内部提供了一个 Endpoint, 对应的 Endpoint ID 为 nacos-config,其 Actuator Web Endpoint URI 为 /actuator/nacos-config。
注:使用 Nacos Config Spring Cloud 1.x 版本的话,其 URI 地址则为 /nacos-config
其中,Endpoint 暴露的 json 中包含了三种属性(即服务响应内容):
- NacosConfigProperties: 当前应用 Nacos 的基础配置信息。
- RefreshHistory: 配置刷新的历史记录。
- Sources: 当前应用配置的数据信息
{
"NacosConfigProperties":{
"serverAddr":"127.0.0.1:8848",
"username":"",
"password":"",
"encode":null,
"group":"DEFAULT_GROUP",
"prefix":null,
"fileExtension":"properties",
"timeout":3000,
"maxRetry":null,
"configLongPollTimeout":null,
"configRetryTime":null,
"enableRemoteSyncConfig":false,
"endpoint":null,
"namespace":null,
"accessKey":null,
"secretKey":null,
"contextPath":null,
"clusterName":null,
"name":null,
"sharedConfigs":null,
"extensionConfigs":null,
"refreshEnabled":true,
"sharedDataids":null,
"refreshableDataids":null,
"extConfig":null,
"configServiceProperties":{
"secretKey":"",
"namespace":"",
"username":"",
"enableRemoteSyncConfig":"false",
"configLongPollTimeout":"",
"configRetryTime":"",
"encode":"",
"serverAddr":"127.0.0.1:8848",
"maxRetry":"",
"clusterName":"",
"password":"",
"accessKey":"",
"endpoint":""
}
},
"RefreshHistory":[
],
"Sources":[
{
"lastSynced":"2020-09-14 11:11:37",
"dataId":"nacos-config-sample.properties"
},
{
"lastSynced":"2020-09-14 11:11:37",
"dataId":"nacos-config-sample"
}
]
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 参考书籍
重磅下载 | Java 开发者必备手册《Spring Cloud Alibaba 从入门到实战》,阿里双11同款!-阿里云开发者社区 (aliyun.com) (opens new window)