Bootstrap

微服务学习-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,定时任务当然也就不生效了。

详细原因:

  1. @RefreshScope 注解标注了 @Scope 注解,并默认了 ScopedProxyMode.TARGET_CLASS 属性,此属性的功能就是创建一个代理,在么次调用的时候都用它来调用 GenericScope#get 方法来获取 bean 对象。
  2. 在 GenericScope 里面包装了一个内部类 BeanLifecycleWrapperCache 来对加了 @RefreshScope 的 bean 进行缓存,使其在不刷新时获取的都是同一个对象。
  3. 如果属性发生变更会调用 ContextRefresh#refresh() --> RefreshScope#refreshAll() 进行缓存清理方法调用,并发送刷新事件通知 --> 调用 GenericScope#destroy 实现清理缓存。
  4. 当下一次使用 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 中央仓库。

;