图源:laiketui.com
如果一个服务要集群部署,即存在多个实例。要对这些实例更新配置文件就相当麻烦。此时我们就需要一个配置管理服务来对服务配置进行统一管理和更新。
Nacos 不仅可以作为服务的注册中心,还可以作为配置管理服务。
首先,要在 Nacos 管理面板中为服务添加配置文件。
在 配置管理->配置列表 页面点击 +
以添加新的配置文件:
这里的 Data ID 是有格式要求的,一般是 服务名-profile.后缀名
的格式,这里的 profile 指的是 Spring 中的 profiles.active
配置项的值,也就是用于区分不同开发环境的标识,比如 dev、test、prd 等。
后缀名没有严格要求,对于
yaml
文件,后缀名可以是yml
或yaml
。
子模块中要使用 Nacos 的配置管理功能,需要添加依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
还需要配置一些必要信息(如服务名、profile、Nacos 配置后缀名等)以便能够正确匹配到 Nacos 中设置的配置文件。
但需要注意的是,这些配置信息不能在application.yml
中填写,因为 SpringBoot 启动后会先从 Nacos 中拉取配置文件,再读取本地配置文件applicaton.yml
,将两者合并作为最终的配置信息。
所以 Nacos 配置管理所需的配置信息要填写到bootstrap.yaml
中,这个文件在application.yml
之前被读取。
spring:
application:
name: shopping-user # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
application.yaml
中的重复配置信息可以被删除。
为了验证可以正确从 Nacos 读取配置信息,可以添加一个接口:
public class UserController {
@Value("${pattern.dateformat}")
private String dateformat;
// ...
@GetMapping("/now")
public Result<String> getNowTime(){
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
return Result.success(time);
}
}
启动子模块 shopping-user 的实例,请求 http://localhost:8081/user/now 接口,就能看到如下输出:
{
"data": "2023-07-17 20:12:51",
"errorMsg": "",
"errorCode": "",
"success": true
}
使用 Nacos 统一管理配置文件的好处是可以进行配置的“热更新”,即在服务实例没有重启的情况下更新配置信息。
默认情况下通过@Value
读取到的配置内容是不会热更新的,比如我们将 Nacos 中的配置信息修改为:
pattern:
dateformat: yyyy/MM/dd HH:mm:ss
再次访问接口不会看到时间格式改变。
如果使用@Value
注解读取配置信息,要使用 Nacos 热更新配置信息,就要给所属类添加@RefreshScope
注解:
@RefreshScope
public class UserController {
@Value("${pattern.dateformat}")
private String dateformat;
// ...
}
现在重启子模块 shopping-user,访问接口,之后再修改 Nacos 配置内容为:
pattern:
dateformat: yyyy年MM月dd日 HH:mm:ss
再次访问接口,可以看到接口返回内容变为:
{
"data": "2023年07月17日 20:19:50",
"errorMsg": "",
"errorCode": "",
"success": true
}
这说明热更新已经生效。
一旦 Nacos 的配置信息发生变化,它就会通知相关的实例,相关的实例就会重新从 Nacos 读取配置信息。
除了使用@Value
读取配置信息,还可以使用@ConfigurationProperties
注解的类来读取配置信息,此时不需要做任何改变就可以从 Nacos 热更新配置信息。
这里改用@ConfigurationProperties
标记的类读取配置信息:
@Component
@ConfigurationProperties(prefix = "pattern")
@Getter
@Setter
public class PatternProperties {
private String dateformat;
}
测试接口修改为:
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@Autowired
private PatternProperties patternProperties;
// ...
@GetMapping("/now")
public Result<String> getNowTime(){
String dateformat = patternProperties.getDateformat();
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
return Result.success(time);
}
}
注意,这里并没有使用
@RefreshScope
注解。
进行类似之前的测试,可以观察到热更新已经生效。从这个角度讲,使用@ConfigurationProperties
读取配置是优于@Value
的。
实际上子模块 shopping-user 启动时可以观察到从多个 Nacos 配置读取信息的记录:
[add-listener] ok, tenant=, dataId=shopping-user, group=DEFAULT_GROUP, cnt=1
[add-listener] ok, tenant=, dataId=shopping-user.yaml, group=DEFAULT_GROUP, cnt=1
[add-listener] ok, tenant=, dataId=shopping-user-dev.yaml, group=DEFAULT_GROUP, cnt=1
这说明如果我们配置不带 profile 的配置文件 shopping-user.yaml,同样会被子模块 shopping-user 读取。
这里 shopping-user.yaml 和 shopping-user-dev.yaml 的关系类似于 application.yml 和 application-dev.yml 的关系。也就是说,对于多环境下共享且需要配置到 Nacos 的配置信息,我们可以保存到 [服务名].[后缀名] 这样的 Nacos 配置文件中。
下面我们来验证一下。
修改 8082 端口对应实例的配置,将 Active profiles 修改为test
:
这是一个快捷设置,也可以在启动参数中添加
-Dprofiles.active=test
。
启动实例,现在我们有两个 shopping-user 实例,一个是 dev 环境,一个是 test 环境。
在 Nacos 上添加配置文件:
shopping-user.yaml
pattern:
common: common propertie value
shopping-user-test.yaml
pattern:
dateformat: yyyy年MM月dd日 HH:mm:ss
PatternProperties 类添加一个属性:
public class PatternProperties {
private String dateformat;
private String common;
}
增加一个接口:
public class UserController {
@GetMapping("/properties")
public Result<PatternProperties> properties() {
return Result.success(patternProperties);
}
}
请求 http://localhost:8081/user/properties
{
"data": {
"dateformat": "yyyy/MM/dd/ HH:mm:ss",
"common": "common propertie value"
},
"errorMsg": "",
"errorCode": "",
"success": true
}
请求 http://localhost:8082/user/properties
{
"data": {
"dateformat": "yyyy年MM月dd日 HH:mm:ss",
"common": "common propertie value"
},
"errorMsg": "",
"errorCode": "",
"success": true
}
这说明 dev 环境的实例会读取 Nacos 上的 [服务名]-dev.[后缀名] 的配置文件,test 环境的实例会读取 [服务名]-test.[后缀名] 的配置文件。但他们都会读取 [服务名].[后缀名] 这个配置文件。
如果 shopping.yaml、shopping-dev.yaml、本地的 application.yml 都配置了同一个配置项,那么优先级顺序是怎样的?
可以用以下的方式进行验证。
修改 shopping-user.yaml
pattern:
common: common propertie value
common-and-dev: in common file
common-and-local: in common file
修改 shopping-user-dev.yaml
pattern:
dateformat: yyyy/MM/dd/ HH:mm:ss
common-and-dev: in dev file
修改 shopping-user 的本地配置文件 application.yml:
pattern:
common-and-local: in local file
修改 PatternProperties.java
public class PatternProperties {
private String dateformat;
private String common;
private String commonAndDev;
private String commonAndLocal;
}
重新运行 shopping-user 实例并访问接口 http://localhost:8081/user/properties
{
"data": {
"dateformat": "yyyy/MM/dd/ HH:mm:ss",
"common": "common propertie value",
"commonAndDev": "in dev file",
"commonAndLocal": "in common file"
},
"errorMsg": "",
"errorCode": "",
"success": true
}
这说明具体环境的 Nacos 配置文件(这里是 shopping-dev.yaml)优先于通用 Nacos 配置文件(这里是 shopping.yaml),通用 Nacos 配置文件优先于本地配置文件(这里是 application.yml)。
换言之,远程配置文件(Nacos 上的配置文件)优先于本地配置,而具体环境的远程配置文件要优先于通用的远程配置文件。
The End,谢谢阅读。
本文的完整示例代码可以从这里获取。