微服务学习-Nacos 配置中心实战
1. Nacos 配置中心配置方法的变化
在 SpringBoot2.4 这个大版本中一项非常重要的改动:出于对云原生多配置文件的支持,默认关闭了对 bootstrap.yml 的使用。
解决方案:
方案一:重新启用 bootstrap.yml(不推荐)
方案二:使用 spring.config.import(官方推荐)
spring:
application:
name: icoolkj-mall-user-config-demo
cloud:
nacos:
config: # nacos 配置中心
server-addr: icoolkj-mall-nacos-server:8848
namespace: dev # 指定 dev 命名空间
username: nacos
password: nacos
file-extension: yml # 指定配置文件扩展名为yml
config:
import:
- optional:nacos:${spring.application.name}.yml
- nacos:nacos-discovery.yml # nacos 注册中心配置
spring.config.import: - optional:nacos:icoolkj-mall-user-config-demo.yml:这一行是 Spring Boot 2.4.0 及以上版本引入的配置文件导入机制。optional:nacos:icoolkj-mall-user-config-demo.yml 表示从 Nacos 配置中心导入名为 icoolkj-mall-user-config-demo.yml 的配置文件,其中 optional 关键字意味着如果该配置文件在 Nacos 中不存在,那么 Spring Boot 将不会抛出异常,而是继续执行后续的初始化流程。
2. 准备测试环境
会员服务:icoolkj-mall-user-config-demo
订单服务:icoolkj-mall-order01-config-demo
速通版:git checkout v2.30 版本:
icoolkj-microservices-code 标签 - Gitee.com
启动会员、订单服务,获取会员订单信息:
http://localhost:8580/api/user/getOrderByUserId?userId=1
3. 多套环境粒度配置
在日常开发中如果遇到多套环境下的不同配置,可以通过 Spring 提供的 ${spring.profiles.active} 来配置。
以订单服务为例,配置开发环境、生产环境、其他环境
3.1. 演示环境配置
3.1.1. OrderController 增加测试方法:
@Value("${order.quantity}")
private int quantity;
@PostMapping("post1")
public Result<OrderResponse> post1(@RequestBody OrderRequest orderRequest) {
OrderResponse orderResponse = new OrderResponse();
// 设置数量
orderResponse.setOrderQuantity(quantity);
return Result.ok(orderResponse);
}
3.1.2. 同时在 application.yml 中增加配置:
order:
quantity: 10
3.1.3. Nacos 配置中心增加开发环境、生产环境、其他环境配置文件(文件路径:nacos-examples/config)
icoolkj-mall-user-config-demo-dev.yml
icoolkj-mall-user-config-demo-prod.yml
icoolkj-mall-user-config-demo-other.yml
3.2. 修改 application.yml 配置
3.2.1. 配置 spring.profiles.active 为开发环境
spring:
application:
name: icoolkj-mall-order01-config-demo
profiles:
active: dev
3.2.2. import 中引入 ${spring.application.name}-${spring.profiles.active}.yml 配置
spring:
config:
import:
- optional:nacos:${spring.application.name}.yml
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yml
- nacos:nacos-discovery.yml
- nacos:db-mysql-common.yml
3.2.3. 测试
重启订单服务,调用 localhost:8580/api/user/post1 ,查看测试结果;
配置 jvm 参数 -Dspring.profiles.active=prod,重启服务,调用 localhost:8580/api/user/post1 ,查看测试结果;
4. 自定义 Namespace 的配置
4.1. 用于进行租户粒度的隔离
不同的命名空间下,可以存在相同的 Group 或 Data Id 的配置。
在没有明确指定 ${spring.cloud.nacos.config.namespace} 配置的情况下,默认使用的是 Nacos 上 Public 这个 namespace。
spring:
cloud:
nacos:
config:
server-addr: icoolkj-mall-nacos-server:8848
namespace: dev # 指定 dev 开发环境命名空间
username: nacos
password: nacos
4.2. 测试
4.2.1. Nacos 配置克隆
新建 kevin 命名空间
将 dev 命名空间下的配置克隆到 kevin 命名空间
克隆完成,查看结果。
4.2.2. 配置 application.yml 中的 spring.cloud.nacos.config.namespace 配置
spring:
cloud:
nacos:
config:
server-addr: icoolkj-mall-nacos-server:8848
namespace: kevin # 指定 dev 开发环境命名空间
username: nacos
password: nacos
重启订单服务,查看控制台是否能够拉取配置。
5. 自定义 Group 的配置、
主要用于区分不同的微服务或应用组件
一个应用可能使用了 database_user 配置 和 MQ_topic 配置,可以将这些配置分别划分不同的 Group 中,以便更好地管理和维护。
在没有明确指定 ${spring.cloud.nacos.config.group} 配置情况下,默认使用的组 DEFAULT_GROUP。
spring:
cloud:
nacos:
discovery:
server-addr: icoolkj-mall-nacos-server:8848
namespace: dev # 指定 dev 开发环境命名空间
group: group1 # 指定 group1 分组
username: nacos
password: nacos
6. 配置的优先级
按 import 的配置顺序加载,重复的配置会被覆盖
spring:
config:
import:
- optional:nacos:${spring.application.name}.yml
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yml
- nacos:nacos-discovery.yml
- nacos:db-mysql-common.yml
例如本地也配置了 order.quantity,nacos 配置也有对应环境的 order.quantity;按照上面代码顺序会覆盖本地。
7. 配置的动态刷新
spring-cloud-starter-alibaba-nacos-config 支持配置的动态更新。
7.1. 测试
当动态配置刷新时,会更新到 Enviroment 中。
7.1.1. 修改启动类,每隔 3s 从 Enviroment 中获取 order.quantity 的值,并重启订单服务
7.1.2. 进入 Nacos 配置中心,修改 icoolkj-mall-user-config-demo-dev.yml 的配置
注意:修改时候注意环境配置和所属的命名空间,不要修改错文件。
查看控制台:order.quantity 的值 888 变成 88
7.1.3. 调用接口,查看结果
调用 localhost:8580/api/user/post1,查看 quantity 是否从 888 变成 88
7.2. 结论
可以从 Environment 获取到配置中心更改后的值,蛋是 OrderController(Bean 对象)中 @Value 修饰的值没有变化。
7.3. 思考
如何实现 Spring 管理的 Bean 对象中 @Value 修饰的属性的动态更新。
7.4. 解决方案
使用 @RefreshScope 注解实现 Bean 的动态刷新
使用 @RefreshScope 修饰的 OrderController,访问 /api/order/post1 接口可以获取最新的值。
@RefreshScope
@Slf4j
@RestController
@RequestMapping("/api/order")
public class OrderController {
重启订单服务,测试。按照 7.1 步骤进行测试。查看 quantity 是否从 888 变成 88
7.5. 注意
@RefreshScope 使用不当会导致 @Scheduled 定时任务失效
7.5.1. 场景重现
当利用 @RefreshScope 刷新配置会导致定时任务失效
@EnableScheduling
@EnableDiscoveryClient
@SpringBootApplication
public class Order01ConfigDemoApplication {
OrderController 增加定时任务
@RefreshScope
@Slf4j
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Value("${order.quantity}")
private int quantity;
@PostMapping("post1")
public Result<OrderResponse> post1(@RequestBody OrderRequest orderRequest) {
OrderResponse orderResponse = new OrderResponse();
// 设置数量
orderResponse.setOrderQuantity(quantity);
return Result.ok(orderResponse);
}
// 触发 @RefreshScope 执行逻辑会导致 @Scheduled 定时任务失效
// 定时任务每隔 3s 执行一次
@Scheduled(cron = "*/3 * * * * ?")
public void scheduled() {
System.out.println("定时任务正常执行...");
}
}
7.5.2. 测试
当在 Nacos 配置中心变更属性值后,定时任务失效;
当再次访问 /api/order/post1 接口时,定时任务又生效了;
7.5.3. 原因分析
@RefreshScope 修改的 Bean 的属性发生变更后,会从缓存中清除。此时没有这个 bean,定时任务当然也就不生效了。
详细原因:
- @RefreshScope 注解标注了 @Scope 注解,并默认了 ScopedProxyMode.TARGET_CLASS 属性,此属性的功能就是创建一个代理,在么次调用的时候都用它来调用 GenericScope#get 方法来获取 bean 对象。
- 在 GenericScope 里面包装了一个内部类 BeanLifecycleWrapperCache 来对加了 @RefreshScope 的 bean 进行缓存,使其在不刷新时获取的都是同一个对象。
- 如果属性发生变更会调用 ContextRefresh#refresh() --> RefreshScope#refreshAll() 进行缓存清理方法调用,并发送刷新事件通知 --> 调用 GenericScope#destroy 实现清理缓存。
- 当下一次使用 bean 对象时,代理对象调用 GenericScope#get(String name, ObjectFactory<?> objectFactory) 方法创建一个薪的 bean 对象,并存入缓存中,此时薪对象因为 Spring 的装配机制就是薪的属性了。
7.5.4. 解决方案
实现 Spring 事件监听,监听 RefreshScopeRefreshedEvent 事件,监听方法中进行一次定时方法调用。
@RefreshScope
@Slf4j
@RestController
@RequestMapping("/api/order")
public class OrderController implements ApplicationListener<RefreshScopeRefreshedEvent> {
@Value("${order.quantity}")
private int quantity;
@PostMapping("post1")
public Result<OrderResponse> post1(@RequestBody OrderRequest orderRequest) {
OrderResponse orderResponse = new OrderResponse();
// 设置数量
orderResponse.setOrderQuantity(quantity);
return Result.ok(orderResponse);
}
// 触发 @RefreshScope 执行逻辑会导致 @Scheduled 定时任务失效
// 定时任务每隔 3s 执行一次
@Scheduled(cron = "*/3 * * * * ?")
public void scheduled() {
System.out.println("定时任务正常执行...");
}
@Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
this.scheduled();
}
}
重启测试。
7.5.5. OpenFeign 开启对 feign.Request.Options 属性的刷新支持
官网说明:Spring Cloud OpenFeign
#启用刷新功能,可以刷新 connectTimeout 和 readTimeout
spring.cloud.openfeign.client.refresh-enabled=true
8. 拓展:Nacos 插件
8.1. 需求
为了保证用户敏感配置数据的安全,对配置中心的配置数据加密。
8.2. 解决方案
使用 Nacos 加密插件
官方文档:配置加密
在 Nacos 服务端启动的时候就会加载所有依赖的加密算法,然后通过发布配置的 dataId 的前缀(cipher-[加密算法名称])来进行匹配是否需要加密和使用的加密算法。
客户端发布配置会在客户端通过 Filter 完成加解密,也就是配置在传输过程中都是密文的。而控制台发布的配置会在服务端进行处理。
注意:目前插件需要自己编译,并未上传至 maven 中央仓库。