Spring Cloud Alibaba:Nacos配置中心
动态配置服务可以以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
Nacos
提供了一个简洁易用的
UI
帮助管理所有的服务和应用的配置。
Nacos
还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助更安全地在生产环境中管理配置变更和降低配置变更带来的风险。
博主之前介绍过
Spring Cloud
提供的配置中心
Config
组件:
- Spring Cloud 之Config配置中心-使用Bus组件实现配置动态更新
但
Config
组件是不负责存储和管理配置文件的(先不管配置文件的缓存),配置文件存储在第三方平台上(如
Github
),并且该平台需要有
Webhook
的功能,当配置文件被修改后,该平台通过
Webhook
的功能去回调
Config Server
的接口,通知
Config Server
配置文件更新了,之后
Config Server
还需要使用
MQ
将修改的配置文件传输给相应的
Config Client
,因此
Config Client
也需要与
MQ
绑定。看起来
Config
组件并不是很灵活,博主接下来会介绍使用
Nacos
作为配置中心,
Nacos
不同于
Config
,
Nacos
本身负责存储和管理配置文件,因此不需要第三方平台的介入,通过结合
push
和
pull
两种方式来实现动态配置服务,并且还提供简洁易用的
UI
帮助管理所有的服务和应用的配置。
上一篇博客中已经介绍了使用
Nacos
作为服务注册与发现中心,关于
Nacos
服务的安装与运行请参考下面这篇博客:
- Spring Cloud Alibaba:Nacos服务注册与发现
创建服务
创建
AlibabaBlog maven
工程作为父
module
,再创建
config
子
module
。
AlibabaBlog module
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kaven</groupId>
<artifactId>AlibabaBlog</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<description>Spring Cloud Alibaba</description>
<modules>
<module>config</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring-cloud-alibaba-version>2.2.6.RELEASE</spring-cloud-alibaba-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<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>
</project>
config module
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>AlibabaBlog</artifactId>
<groupId>com.kaven</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>config</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
bootstrap.yml
(这里不是
application.yml
):
spring:
application:
name: config
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
ConfigController
接口类:
package com.kaven.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: ITKaven
* @Date: 2021/11/09 12:57
* @Leetcode: https://leetcode-cn.com/u/kavenit
* @Notes:
*/
@RestController
@RefreshScope
public class ConfigController {
@Value("${kaven}")
private String kaven;
@GetMapping("/config")
public String getConfig() {
return kaven;
}
}
@RefreshScope
注解需要加上,以便让
config
服务感知到这里有需要动态更新的配置文件参数。
ConfigApplication
启动类:
package com.kaven.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @Author: ITKaven
* @Date: 2021/11/09 11:43
* @Leetcode: https://leetcode-cn.com/u/kavenit
* @Notes:
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class);
}
}
@EnableDiscoveryClient
注解要加上。
为什么是
bootstrap.yml
而不是
application.yml
:
构建于
Spring Cloud
之上,在
Spring Boot
中有两种上下文,一种是
Spring Boot
, 另外一种是
bootstrap
,
application
是应用程序的父上下文,也就是说
bootstrap
加载优先于
bootstrap
。
applicaton
主要用于从外部资源中加载配置信息。这两个上下文共用一个环境,它是任何
bootstrap
应用程序的外部属性的来源。
Spring
里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。
bootstrap
配置文件
在
Nacos
上增加一个配置文件
config.yaml
,为什么这样命名等下再解释。
config.yaml
配置文件内容如下所示:
server:
port: 9000
kaven: "hello kaven"
启动
config
服务,很显然服务成功获取了
config.yaml
配置文件(不然端口默认是
8080
)。
config
服务也在
Nacos
上注册成功了。
请求
config
服务的接口(
http://localhost:9000/config
),显示
config.yaml
配置文件中的内容。
修改
Nacos
中
config.yaml
配置文件
kaven
参数的值。
config
服务会感知到
config.yaml
配置文件的更新。
再次请求
config
服务的接口,就会显示更新后的值了。
Data ID
在
Nacos
中,
Data ID
的完整格式如下:
${prefix}-${spring.profiles.active}.${file-extension}
prefix
默认为
spring.application.name
的值,也可以通过配置项
spring.cloud.nacos.config.prefix
来配置。
spring.profiles.active
即为当前环境对应的
profile
(为空时,对应的连接符
-
也将不存在,因此
Data ID
的拼接格式变成:
${prefix}.${file-extension}
file-exetension
为配置文件的后缀,可以通过配置项
spring.cloud.nacos.config.file-extension
来配置。
config
服务没有设置
profile
,并且
prefix
为
config
(默认为
spring.application.name
的值),
file-extension
为
yaml
,因此
config.yaml
配置文件会被
config
服务获取到。
但通过
config
服务的后台可以看见,
config
服务会去获取
config
和
config.yaml
这两个配置文件。很显然
config
是
prefix
的值,并且不加如下文件后缀:
.${file-extension}
这里博主来验证一下
config
服务是否会去获取
config
配置文件,将
config.yaml
配置文件中的
kaven
参数删除,再创建
config
配置文件,并且在
config
配置文件中添加
kaven
参数,如果
config
服务没有获取
config
配置文件,就会报错。
重新启动
config
服务,服务并没有报错。
请求
config
服务的接口,显示
config
配置文件中的内容。
所以服务会去获取如下所列的这些配置文件(如果存在,配置文件的优先级规则也是这个顺序,从高到低,大家可以自己去测试一下):
${prefix}-${spring.profiles.active}.${file-extension}
${prefix}.${file-extension}
${prefix}
共享配置文件
如果一些服务中的某些配置是相同的,比如
Redis
或者
MQ
等中间件集群的配置,如果没有共享配置文件,这些服务的配置文件中都要重复这些配置,而当服务数量特别多时,这就不方便配置文件的管理了,比如当
Redis
集群配置发生改变,就需要修改每个依赖该
Redis
集群的服务的配置文件,这需要很大的人力成本和时间成本,所以共享配置文件是有必要的。
而在
Nacos
中实现共享配置文件也特别方便,修改
config
服务的
bootstrap.yml
配置文件:
spring:
application:
name: config
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
prefix: config
shared-configs[0]:
data-id: redis.yaml
refresh: true
extension-configs[0]:
data-id: mq.yaml
refresh: true
profiles:
active: test
其中
shared-configs
和
extension-configs
这两个配置都可以实现共享配置文件的功能。
shared-configs[0]:
data-id: redis.yaml
refresh: true
extension-configs[0]:
data-id: mq.yaml
refresh: true
并且
shared-configs
和
extension-configs
都是
List
类型的数据,因此在它们的后面都加上了
[0]
下标(从
0
开始,下一个就是
[1]
)。
Config
类(
dataId
和
refresh
需要进行设置,
group
使用默认值即可):
public static class Config {
/**
* 扩展配置的数据ID
*/
private String dataId;
/**
* 扩展配置组,默认值为DEFAULT_GROUP
*/
private String group = "DEFAULT_GROUP";
/**
* 是否支持动态刷新,默认不支持
*/
private boolean refresh = false;
...
}
在
Nacos
中创建这些配置文件:
配置文件内容依次如下:
config.yaml: "config.yaml"
config: "config"
config-test.yaml: "config-test.yaml"
redis.yaml: "redis.yaml"
mq.yaml: "mq.yaml"
修改
config
服务的接口:
package com.kaven.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: ITKaven
* @Date: 2021/11/09 12:57
* @Leetcode: https://leetcode-cn.com/u/kavenit
* @Notes:
*/
@RestController
@RefreshScope
public class ConfigController {
@Value("${config.yaml}")
private String configYaml;
@Value("${config}")
private String config;
@Value("${config-test.yaml}")
private String configTestYaml;
@Value("${redis.yaml}")
private String redisYaml;
@Value("${mq.yaml}")
private String mqYaml;
@GetMapping("/config")
public String getConfig() {
return configYaml.concat(" ")
.concat(config).concat(" ")
.concat(configTestYaml).concat(" ")
.concat(redisYaml).concat(" ")
.concat(mqYaml);
}
}
请求
config
服务的接口(配置文件中没有配置服务端口,因此是默认端口
8080
):
很显然这些配置文件获取成功了。
这些配置文件的优先级(优先级从高到低):
config-test.yaml
config.yaml
config
mq.yaml
redis.yaml
因此配置文件的优先级规则如下(如果存在,优先级从高到低):
${prefix}-${spring.profiles.active}.${file-extension}
${prefix}.${file-extension}
${prefix}
${extension-configs}
${shared-configs}
在
config
服务的日志输出中也有体现:
Located property source: [BootstrapPropertySource {name='bootstrapProperties-config-test.yaml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-config.yaml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-config,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-mq.yaml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-redis.yaml,DEFAULT_GROUP'}]