Nacos 作为配置中心的使用探究

配置的演进说明

传统的本地配置

实际配置案例

在正式接入 Nacos 远程配置中心之前,我们必须先来看看传统的、本地的配置是怎么配合 Maven Profiles 玩转多环境切换的。在传统的做法中,为了隔离 dev(开发)、test(测试)、prod(生产)的配置,我们会在 src/main/resources 下整齐地排开 4 个 配置文件:

1
2
3
4
5
src/main/resources/
├── application.yml # 主配置文件(总指挥官)
├── application-dev.yml # 开发环境差异化配置
├── application-test.yml # 测试环境差异化配置
└── application-prod.yml # 生产环境差异化配置

实现本地多环境切换只需要以下三步:

第一步:修改 pom.xml 开启 Maven 的资源过滤和环境切换支持

1
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
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>

<profiles>
<profile>
<id>dev</id>
<properties>
<profile.active>dev</profile.active>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<profile.active>prod</profile.active>
</properties>
<!--通过 activation 实现环境的切换-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>

第二步:配置 application.yml(主指挥官)

这里我们准备了以下配置文件:

  • 主配置文件:application.yml
  • 公共配置文件:application-common.yml
  • 各个环境的配置文件:
    • 开发环境:application-dev.yml、application-db-dev.yml
    • 生产环境:application-prod.yml、application-db-prod.yml

所以,我们可以在主配置文件中做如下配置,就可以根据 maven activation 的不同,激活不同环境的配置文件。

1
2
3
4
5
6
7
spring:
profiles:
# 传统标准写法:利用 @...@ 表达式,强行读取 pom.xml 里的 <profile.active> 变量
# 当你打包选择 dev 时,Maven 会在编译期把这里硬编码改写成 active: dev
## 在旧版本中,大家喜欢写 ${profile.active}。但在 Spring Boot 3.x(Spring Cloud 2023)中,${...} 语法与 Spring
## 自身的属性占位符会产生严重的解析冲突。因此,新版强制且唯一推荐使用 @profile.active@(双艾特符号)来承接 Maven 变量!
active: @profile.active@, db-@profile.active@, common

第三步:实际打包命令

1
2
3
4
5
# 1. 打出测试环境的 jar 包
mvn clean package -Ptest

# 2. 打出生产环境的 jar 包
mvn clean package -Pprod -Dmaven.test.skip=true

更详细的这方面的资料,请参考 《Maven资源文件的打包与过滤》


传统配置的缺点

虽然这套方案陪伴了互联网行业很多年,但在微服务高并发时代,暴露了以下三个致命的缺陷:

  • 配置死结——改个配置必须重启服务:假如线上的 Redis 密码改了,或者你想临时把日志级别从 INFO 调到 DEBUG 排查个 Bug。对不起,你必须改代码、重新 mvn package、重新在生产环境上架部署。在这个过程中,业务直接中断。
  • 安全裸奔——敏感密码物理暴露:生产环境的真实数据库密码、阿里云的 AccessKey 全都明文写在 application-prod.yml 里。任何能进 Git 仓库的开发人员都能看个精光,安全审计直接零分。
  • 配置分散——多模块配置难以集中维护:当你的模块变成了 user、order、stock、payment 等几十个时,每个模块里都要硬生生排开 4 个 YML,一旦想统一加一个公共参数,你要去翻几十个 YML 文件,改到怀疑人生。


配置演进中各工具的比较

在微服务架构的演进史中,配置中心的进化是一部血泪交织的 “去本地化、去重启化” 的斗争史。从最早本地配置的捉襟见肘,到数字化转型的阵痛,行业内先后涌现出了三代主流的分布式配置中心:Spring Cloud Config(初代拓荒者)、Apollo(二代集大成者),以及如今一统江湖的 Nacos(三代统治者)。

  • Spring Cloud Config 是典型的“拼凑型”产物。它自己不存数据,必须在后台挂一个 Git 仓库。当你修改了 Git 里的配置后,微服务默认是感知不到的。如果想让线上 100 个微服务实例刷新配置,你必须在项目里引入 RabbitMQ 或 Kafka,然后搭建 Spring Cloud Bus(消息总线)。改完配置后,你需要手动向网关发一个 /actuator/bus-refresh 的 POST 请求,让 MQ 去广播通知所有机器刷新。改个配置还要架构师先去运维一套 MQ 集群,这种高成本的割裂体验很快就被中小型团队抛弃了。

  • 携程的 Apollo 是一个非常好的产品,在安全审计、版本回滚、灰度发布方面做到了企业级的极致。但是它太重了。 哪怕你只想在一个小型项目里测试一下,你都必须部署 Apollo-Portal(前端展示)、Apollo-AdminService(后台管理)、Apollo-ConfigService(核心服务),并且每个环境还要独立的数据库。这种“杀鸡用重型导弹” 的架构,让很多中小型企业望而却步。

  • 阿里开源的 Nacos(Naming and Configuration Service)之所以能后来居上,是因为它精准地在产品能力、运维成本、生态融合之间找到了那个近乎完美的颠覆平衡点。

    • 在 Nacos 出现之前,一个标准的 Spring Cloud 微服务集群,其基础设施标配是:Eureka(注册中心) + Config(配置中心) + MQ(做刷新),开发人员要维护三套完全不同的中间件。Nacos 直接把 “服务发现(Naming)” 和 “配置管理(Configuration)” 合二为一。引入一个 Nacos,直接替代了 Eureka、Config、Bus、MQ。

    • Nacos 抛弃了笨重的 Git 和 MQ 广播,1.x/2.x 采用高效的 HTTP 长轮询(Long Polling)监听机制,而 3.x/现代版本更是直接升级为了 gRPC 长连接流式推送。要你在代码里加上一个 @RefreshScope 注解,你在 Nacos 网页控制台上点一下“发布”,远程服务器在几毫秒之内就能通过高效通道全自动完成内存变量替换。

    • Nacos 适配了 “多 active 写多个” 的积木式架构。它在 YML 里原生提供了 extension-configs(扩展配置)和 shared-configs(共享配置)的数组列表,这种设计让微服务之间的公共配置提取变得极其优雅。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      spring:
      cloud:
      nacos:
      config:
      server-addr: 127.0.0.1:8848
      file-extension: yml
      # 像拼积木一样,把远程 Nacos 里的各种独立配置片段揉进当前微服务
      extension-configs:
      - data-id: common-database.yml
      refresh: true # 允许独立动态刷新
      - data-id: common-redis.yml
      refresh: false # 敏感配置不允许刷新
      - data-id: common-logback.yml
      refresh: true

从传统多环境本地配置,演进到现代配置中心,行业经历了从 “硬编码切换” 到 “MQ广播外挂”、再到 “平台完备重构” 的探索。最终,Nacos 凭借着 “二合一的极简架构、gRPC的高性能推送、零侵入的动态刷新、以及背靠强大的 Spring Cloud Alibaba 生态”,成为了当下微服务开发的基础组件。


Nacos 作为配置中心的使用

基本使用

接入 Nacos 作为配置中心,最核心的思维转变是:本地只留连接信息(带路党),具体业务配置(如端口、数据库)全部上缴给 Nacos(大本营)。在现代 Spring Boot 3.x / Spring Cloud 2023 生态中,Spring 官方出于安全和架构清晰度考虑,默认把原来历史版本里自动读取 bootstrap.yml 的逻辑给关闭了。为了不引入额外的老旧兼容依赖,我们直接使用现代且最规范的 application.yml 来写这个最基础的案例。

新建 zdemo-nacos-config 模块,这个基础案例本地只需要两个文件,极其干净:

1
2
3
4
zdemo-nacos-config/
├── pom.xml
└── src/main/resources/
└── application.yml # 唯一的本地配置文件(只写 Nacos 连接凭证)

第一步:在 pom.xml 中引入 Nacos 配置中心 Starter

确保你的 Spring Boot 版本是 3.x,Spring Cloud Alibaba 版本匹配(如 2023.x)。在你的 dependencies 节点中引入以下核心依赖:

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>

第二步:编写本地 application.yml(配置连接凭证)

现在本地不放任何业务参数,只告诉 Spring 去哪里找 Nacos,以及找哪一个配置文件(Data ID)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
application:
name: zdemo-nacos-config # 微服务应用名
cloud:
nacos:
config:
server-addr: 192.168.1.149:8848 # Nacos 的服务端地址
file-extension: yml # 远程配置文件的后缀名,支持 properties 或 yml
username: nacos
password: nacos
namespace: prod # 命名空间业务配置隔离,一般可以配置为环境参数
group: DEFAULT_GROUP # 和命名空间类似,它是对应用更细粒度的管理(如机房或项目组)
cluster-name: DEFAULT # Nacos的集群名称,多集群部署会用到,尽量显式指定
config:
# Spring Boot 3.x 核心规定:必须通过 import 方式把远程 Nacos 配置无缝导入进来
import:
# 这一行是现代架构的精髓。它告诉项目:一启动,立刻去 Nacos 抓取名为 zdemo-nacos-config.yml 的文件
# 并且后面带着的 ?refresh=true 代表开启秒级动态刷新
- nacos:zdemo-nacos-config.yml?refresh=true

第三步:在 Nacos 控制台上创建对应的配置文件

  • 启动你的本地 Nacos 实例,浏览器访问 http://192.168.1.149:8848/nacos 并登录。

  • 进入 配置管理 -> 配置列表,点击右上角的 “+” (创建配置) 按钮。

  • 严格按照以下内容填写:

    • Data ID: zdemo-nacos-config.yml (必须和本地应用名及扩展名完全对齐)

    • Group: DEFAULT_GROUP (默认分组)

    • 配置格式: YAML

    • 配置具体内容,例如:

      1
      2
      3
      4
      5
      6
      server:
      port: 8085 # 直接把端口写在云端

      user:
      username: "张三"
      age: 25
    • 点击发布。

第四步:编写 Controller 测试动态刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;

@RestController
@RefreshScope // 核心杀手锏:这个注解意味着一旦远程 Nacos 属性变了,当前 Controller 里的变量会瞬间刷新
public class ConfigController {

/**
* 通过经典的 @Value 强行注入 Nacos 里的远程配置
*/
@Value("${user.username}")
private String username;
@Value("${user.age}")
private int age;

@GetMapping("/config/get")
public String getConfigInfo() {
return "从Nacos读取到的数据 -> 姓名: " + username + ", 年龄: " + age;
}
}

运行你的 Spring Boot 启动类。你会惊奇地发现:

  • 虽然本地 application.yml 里根本没有配置 server.port,但控制台非常听话地打出了:Tomcat initialized with port(s): 8085 (http),说明它已经成功被 Nacos 云端接管!
  • 打开浏览器访问:http://127.0.0.1:8085/config/get 页面精准吐出正确的结果。
  • 重新回到 Nacos 网页控制台,点击修改刚才的 zdemo-nacos-config.yml,点击发布。回到控制台,你会看到项目日志刷出一行刷新通知。此时直接刷新浏览器页面将会看到新的修改后的返回结果。

完全没有重启服务器,数据在毫秒级内瞬间发生了内存平移置换,这就是现代微服务分布式配置中心的强悍之处。


共享配置与扩展配置

共享和扩展配置介绍

在实际生产中,随着微服务模块(如 user、order、stock)越来越多,你会发现每个服务的 YML 里都有大约 70% 的内容是高度重复的:比如极其冗长的 MySQL 连接池参数、Redis 集群地址、RocketMQ 账户、以及统一的日志滚动策略。如果全都复制粘贴,一旦中间件换了 IP 或密码,架构师就要去改几十个服务的配置文件,简直是运维灾难。Nacos 提供的 extension-configs 和 shared-configs 就是为解决这个痛点而生的。

  • shared-configs(共享配置)

    • 定位:跨项目、跨业务线的超全局配置。
    • 生产示例:全公司所有项目通用的公共分布式日志收集组件配置、全局监控埋点配置。
  • extension-configs(扩展配置)

  • 定位:当前项目/当前服务内部、用来做技术组件解耦的垂直配置。
    • 生产示例:把原本塞在 zdemo-nacos-config.yml 里的配置,肢解为 common-mybatis.yml、common-redis.yml。

虽然在技术层面,这两个配置项的功能几乎完全一样,但为了团队规范,一般在微服务模块内部,统一优先使用 extension-configs 来做基础组件的拆解和拼装。


语义和使用上的说明

在 Nacos 页面上,不管你是在做超级全局共享,还是在做某个服务的局部垂直扩展,你都只是在点击 “创建配置”,然后填入一个 Data ID(比如 common-redis.yml)。Nacos 页面上压根就没有一个勾选项叫 “这是共享配置”或 “这是扩展配置”。对于 Nacos 远端而言,它只认三件事:Namespace、Group、Data ID。只要这三要素对上了,它就老老实实把文本吐给客户端。那么既然远端一视同仁,为什么客户端(Spring Cloud 客户端)还要在代码或 YML 里整出 shared-configs 和 extension-configs 两个不同的数组呢?实际使用起来又是不是完全一样呢?这背后的设计逻辑非常微妙,可以用一句话概括:功能完全相通,但 “加载优先级” 和 “团队协作语义” 有着严格的划分。

当发生配置冲突(即多个 YML 里配置了同一个属性,比如 server.port)时,Spring Cloud 客户端内部有着铁打的顺位覆盖规则,终极优先级大排位(由高到低):

  • 微服务自身主配置 (最高)
  • extension-configs (中)
  • shared-configs (最低)
  • 本地 application.yml (底层垫底)。

shared-configs 通常由基础架构组 / 运维组统一维护。放的是全公司、跨项目、所有人必须无条件遵守的死配置(如全局监控埋点 skywalking-agent.yml、公司统一的分布式日志输出规范)。它最先被加载,所以它的优先级最低。任何写在 extension-configs(由项目组内部 / 微服务开发自己维护) 或应用主配置里的同名属性,都能无条件覆盖掉 shared-configs 里的值。

1
2
3
4
5
6
7
8
9
10
11
spring:
config:
import:
# 过去属于 shared-configs(最先加载,优先级最低)
- nacos:global-skywalking.yml?refresh=true

# 过去属于 extension-configs(中间加载,可覆盖上面的全局配置)
- nacos:common-redis.yml?refresh=true

# 自身主配置(最后加载,拥有最高统治权,覆盖一切)
- nacos:zdemo-nacos-config.yml?refresh=true


实际案例

第一步:在 Nacos 控制台上,把公共积木发布好

积木一:公共数据库配置。Data ID: common-mybatis.yml,配置格式: YAML,配置内容:

1
2
3
4
5
6
7
8
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/zdemo_prod?useSSL=false&serverTimezone=UTC
username: prod_root
password: prod_password_666
mybatis-plus:
configuration:
map-underline-to-camel-case: true

积木二:公共 Redis 缓存配置。Data ID: common-redis.yml,配置格式: YAML,配置内容:

1
2
3
4
5
6
7
spring:
data:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 3000ms

第二步:在 zdemo-nacos-config 模块中改写本地 application.yml

现在,我们要让微服务在启动时,像集装箱拼装一样,把主配置和上面两块远程积木全量加载进来,并全部开启动态刷新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
application:
name: zdemo-nacos-config
cloud:
nacos:
config:
server-addr: 192.168.1.149:8848
file-extension: yml
username: nacos
password: nacos
namespace: prod
group: DEFAULT_GROUP
cluster-name: DEFAULT
config:
# 现代 Boot 3.x 积木组装流:利用 import 数组,按顺序从上往下加载和覆盖
import:
# 1. 优先加载最基础的公共积木:MyBatis 配置
- nacos:common-mybatis.yml?refresh=true
# 2. 接着加载公共积木:Redis 配置
- nacos:common-redis.yml?refresh=true
# 3. 最后加载微服务自己的主配置文件(用于存放微服务特有的业务参数、或者覆盖上面的公共参数)
- nacos:zdemo-nacos-config.yml?refresh=true

多配置文件组合时,可能会发生属性冲突(比如 common-mybatis.yml 里配了数据库密码是 prod_password,但你本地开发测试想用 dev_password)。在 Boot 3.x 的 spring.config.import 体系下,其覆盖规则是:由上到下,后发制人

  • 写在 import 列表越靠下方(越靠后)的文件,优先级越高!
  • 如果 zdemo-nacos-config.yml(写在最后)里写了和 common-mybatis.yml(写在最前)一模一样的配置项,最终生效的一定是以最后一行主文件为准。这给予了微服务极强的 “特例微调” 能力。

我们来编写代码验证多积木融合,同时注入来自 common-mybatis.yml 和 common-redis.yml 的远程属性,看看它们是否成功并存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;

@RestController
@RefreshScope
public class MultiConfigController {

// 注入来自 common-mybatis.yml 的密码
@Value("${spring.datasource.password:unkown}")
private String dbPassword;

// 注入来自 common-redis.yml 的端口
@Value("${spring.data.redis.port:6379}")
private int redisPort;

@GetMapping("/config/multi-get")
public String getMultiConfig() {
return "【多配置融合测试】-> 数据库密码: " + dbPassword + " | Redis端口: " + redisPort;
}
}

shared-configs 和 extension-configs 在 nacos 上会否都是 一样的配置文件,没有 区分。实际客户端使用是不是也完全一样呢


各种配置的优先级

为了让你在排查线上生产事故、写自动化部署脚本时胸有成竹,我们直接拉出这套由高到低(从最高统治者到垫底打工人)的终极优先级大排位:

第一,命令行 -- 凭什么绝杀 JVM 的 -D?很多人分不清 –server.port=8087 和 -Dserver.port=8088。

  • “-D” 是 Java 虚拟机参数,它是给 JVM 听的。
  • “–” 是 Spring Boot 专属的应用程序参数,它会被直接传入 main(String[] args)。
  • Spring Boot 认为传入 args 的命令代表着当前最紧急、最直观的现场指令,因此它的优先级死死压制住 JVM 系统属性。

第二,Jar 包外部凭什么绝杀 Jar 包内部?这是 Spring Boot 为了彻底贯彻 “配置与代码分离” 而设计的铁律。

  • 如果你在外面的目录下放了一个 application.yml,Spring Boot 启动时会优先去扫描当前执行目录。只要外面有,它会认为外面的文件是由现场运维人员亲手调优过的,所以包外的任何配置文件都会无条件覆盖包内的同名文件。

第三,Profile(带后缀)凭什么 绝杀 主配置文件?

  • 不管是包内还是包外,application-dev.yml 的优先级永远高于 application.yml。
  • 因为主配置文件是 “大一统” 的泛泛而谈,而 Profile 是精准定位到当前环境(如开发/生产)的特立独行,自然个性配置覆盖共性配置。

第四,Nacos 到底排第几?

  • 在老版本(Spring Boot 2.x 时代),Nacos 依赖 bootstrap.yml 加载,它的优先级是极高的(甚至能干掉包外的 yml)。但在 Spring Boot 3.x 中,由于废弃了 bootstrap,Nacos 完全退化为了一个通过 spring.config.import 导入的普通外部数据流。这意味着:Nacos 的肉身等价于它被 import 进来的那个宿主文件的优先级。但是,由于 import 具有后发制人的叠加态,它能直接反向吞噬掉宿主文件里写在它上方的配置。比如你包内的 application.yml 是怎么写的:

    1
    2
    3
    4
    5
    6
    7
    server:
    port: 8081 # 1. 本地写了 8081

    spring:
    config:
    import:
    - nacos:zdemo-nacos-config.yml?refresh=true # 2. 远程 Nacos 里写了 8085

    按照排卡,这个文件是 “Jar包内的文件”(天梯榜第 6 级)。在这个文件内部,Spring 自上而下解析。解析到第1行,端口是 8081。解析到第 2 行,Spring 突然执行 import 跑到远程把 Nacos 的 8085 拽了下来。因为 import 在下方,Nacos 的 8085 会瞬间把本地的 8081 顶掉!

  • 但如果此时运维在 Jar 包外放了一个 application-prod.yml(天梯榜第 3 级),并在里面写了 server.port=8090。这时候 Nacos 会瞬间破功,因为包外的 Profile 文件在权力等级上(第3级)直接跨阶层碾压了包内文件(第6级),即便 Nacos 在包内用 import 怎么折腾,最终生效率领依然是包外的 8090!


ConfigurationProperties

安全动态刷新

在前面的基础测试中,我们频繁使用了 @Value(“${…}”)。虽然好懂,但在真正的复杂项目中,大量铺满 @Value 会被架构师无情扣分,原因非常现实:

  • 满屏皆是硬编码:如果一个配置类要注入 10 个属性,你就得写 10 个 @Value,代码臃肿且极难维护。
  • 不支持复杂类型:当远程 Nacos 里面配置了 List(数组)、Map(键值对)、或者深层嵌套的对象时,@Value 的解析语法会变得极其诡异甚至直接报类型转换崩溃。
  • @RefreshScope 的高并发隐患:@RefreshScope 底层是通过 CGLIB 动态代理实现的。每次远程配置一变,Spring 会直接销毁旧的 Bean,重新 new 一个新的 Bean。在高并发流量的核心接口上,这种 “瞬时销毁重构” 极易引发短暂的空指针异常(NPE)或主线程卡顿。

Nacos 对 Spring 生态做了极其硬核的底层适配:凡是通过 @ConfigurationProperties 强类型映射的配置类,天然、无条件地支持 “秒级动态刷新”,不需要加任何 @RefreshScope。它的底层是通过直接修改对象的 Field(属性指针)来赋值的,Bean 依然是那个 Bean,没有销毁重构的过程,在高并发生产环境下极其安全稳定。


具体案例

在 Nacos 的 DEFAULT_GROUP 下,修改你的主配置文件 zdemo-nacos-config.yml(或者新建一块专用积木):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 模拟复杂的业务配置
owlias:
system:
app-name: "Owlias-IM"
version: "v1.3"
# 数组/列表类型
allow-ip-list:
- 127.0.0.1
- 192.168.1.100
- 10.0.0.1
# 复杂的内嵌对象(安全策略)
security-policy:
max-retry-count: 5
lock-minutes: 15
enable-captcha: true

我们在 zdemo-nacos-config 模块中,写两个极其干净的纯 Java 类,不需要写任何 @Value 和 @RefreshScope:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;

@Data
@Component // 变成 Spring 容器管理的 Bean
@ConfigurationProperties(prefix = "owlias.system") // 核心:强行绑定 Nacos 里的指定前缀
public class OwliasSystemProperties {
private String appName;
private String version;
private List<String> allowIpList; // 自动解析并注入 YML 数组
private SecurityPolicy securityPolicy; // 自动解析并注入嵌套对象
}

@Data
public class SecurityPolicy {
private Integer maxRetryCount;
private Integer lockMinutes;
private Boolean enableCaptcha;
}

现在我们在接口里直接注入这个配置对象,打印出来看它长什么样:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class PropertiesBoundController {

@Autowired
private OwliasSystemProperties owliasSystemProperties; // 像对待普通的 Service 一样直接注入

@GetMapping("/config/prop-get")
public OwliasSystemProperties getSystemProperties() {
// 直接把整个对象当成 JSON 吐给前端,顺便观测动态刷新的神奇效果
return owliasSystemProperties;
}
}

保持项目运行,当你去 Nacos 控制台上,把 version 改成 “v1.4”,把 maxRetryCount 改成 99,点击发布。回到浏览器刷新页面,你会发现 version 和 maxRetryCount 立马发生了变化,我们的项目完全不需要重启!


Nacos 配置中心的容灾

为什么需要容灾

在分布式微服务架构中,架构师必须秉承一个墨菲定律:任何可能出问题的地方就一定会出问题。试想线上的极端灾难场景:某天机房核心交换机冒烟、网络大面积断瘫,或者 Nacos 集群突然因为硬件故障集体猝死。此时,线上的微服务刚好触发了自动重启或弹性扩容。

如果微服务完全依赖远程 Nacos,一旦连不上大本营,整个集群就会在启动时疯狂抛出 ConnectException,导致所有微服务大面积无法启动,线上业务彻底瘫痪。为了应对这种灾难,Nacos 内置了硬核的 本地快照容灾机制。同时,我们还需要掌握如何利用 本地配置逆向覆盖远程配置 的自救技巧。


本地快照机制

Nacos 客户端在设计之初就考虑到了容灾。当你的微服务健康运行、且顺利连上 Nacos 时,Nacos 客户端会在后台默默干一件事:把从远程拉取到的所有配置,在微服务的本地磁盘上硬拷贝一份,这就是 Snapshot(快照)。快照的存放路径高度依赖当前运行微服务的系统用户:

  • Mac / Linux 环境:存放于 /Users/${user}/nacos/config/
  • Windows 环境:存放于 C:\Users\${user}\nacos\config\

当 Nacos 注册中心彻底挂掉时,客户端会全自动触发 无感容灾降级

  1. 微服务启动,尝试连接远端 Nacos 失败,触发重试超时。
  2. 触发底层的 LocalEncryptedDiskCacheManager(本地磁盘缓存管理器)。
  3. 自动降级去读取上述路径下的 Snapshot 快照文件。
  4. 项目成功启动! 虽然暂时无法动态刷新配置,但由于读取了快照,微服务能够带着历史健康的配置正常提供业务服务,直接给运维和架构师留出了宝贵的抢修时间。


如何假装通过 Nacos 启动项目

有时候在公司局域网或者本地开发时,你根本没装 Nacos,或者想彻底断开网络,只假装通过 Nacos 启动项目。我们可以通过配置 “故障转移文件(Failover)” 来强行欺骗系统。实际可以通过以下步骤完成:

  • 跑到本地的快照目录:/Users/${user}/nacos/config/
  • 在该目录下,手动新建一个名为 data 的文件夹。
  • 进去新建一个名为 zdemo-nacos-config_config 的文件夹(对应你的 Nacos 命名空间/分组)。
  • 在里面扔一个你纯手工编写的配置文件,名字就叫你的 Data ID:zdemo-nacos-config.yml。

此时,即便你把网线拔掉,把本地的 Nacos Server 直接彻底关闭,再启动你的 zdemo-nacos-config 模块,项目依然能完美启动并读取到你在这个神秘文件夹里魔改的代码!


保命的 optional 参数

有时候我们会看到下面这样的配置:

1
2
3
4
spring:
config:
import:
- optional:nacos:zdemo-scloud-user.yml

这段配置的含义是告诉 Spring Boot:请通过 Nacos 协议通道,去云端帮我把 zdemo-scloud-user.yml 这块配置积木运回来融入系统。如果此时 Nacos 恰好挂了或者断网了,没关系(optional),千万别让项目崩溃,直接跳过并降级去读我的本地快照(目录 /Users/${user}/nacos/config/ 的配置,而不是本地项目中写的配置文件)启动即可!

当你在本地配置了 optional:nacos:zdemo-scloud-user.yml 并且突然断网时,Spring Boot 内部的真实运作剧本是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[1. 项目启动]


[2. 读取本地源码中的 application.yml] ──► 拿到应用名、Nacos地址、import带路指令


[3. 试图连接远程 Nacos 拉取 zdemo-scloud-user.yml]

├─► (成功) ──► 1. 成功融合云端配置
│ 2. 并在用户目录刷新本地快照 (Snapshot)

└─► (失败/断网)


[4. 识别到 optional: 关键字] ──► 触发免死金牌,不抛异常崩溃


[5. 降级跳转:跑到用户目录 /Users/zhangqingli/nacos/config/ 寻找快照]

├─► (找到历史快照) ──► 把快照里的“历史云端配置”加载进内存,顺利启动!

└─► (没有快照/首次裸奔) ─► 略过远程数据,仅带着【第2步】里的基础参数硬着头皮启动

这就是为什么在大厂的微服务生产环境中,import 列表里的每一个远程 Data ID 前面,都会雷打不动、齐刷刷地全部加上 optional: 前缀的根本原因,它就是微服务启动的最后一道安全防护网!


如何让本地配置强行逆袭、干掉远程 Nacos

在上一关的 “诸神之战”中,我们得知:在同一个 YML 文件内,由于 import 具有后发制人的特性,写在 import 后面的远程 Nacos 配置会把写在它前面的本地配置无情覆盖。

现在线上有 10 台机器,共用 Nacos 上的 common-database.yml(里面连的是生产数据库 A)。 今天 3 号机器突然发生性能诡异劣化,架构师想让 3 号机器临时改连到一个专门用来抓取 Trace 性能的影子数据库 B。

  • 错误做法:直接去 Nacos 上改 common-database.yml。这样会导致 10 台机器全部被动态刷新、一起连过去,整个生产环境被瞬间污染。
  • 正确做法:利用本地配置强行逆袭重写权。

由于 Nacos 默认把 import 写在本地 application.yml 的最后一行,导致本地属性无法战胜它。阿里的架构师为了解决这个问题,在 Nacos 客户端内内置了一个核级防御开关。只要你在想搞特殊的那台机器的本地 application.yml 或者是启动命令行中,追加以下参数:

1
2
# 生产保命金牌开关:允许本地配置覆盖远程配置(默认是 false)
spring.cloud.nacos.config.override-remote-config=true

一旦将 override-remote-config 设置为true,整个天梯榜的底层逻辑发生逆转。当 Spring 发现本地 YML 里的例如 spring.datasource.url 和远程 Nacos 里的发生撞车时,它会选择无条件信任本地的配置,直接把拉取回来的 Nacos 远程属性扔进垃圾桶。这样你就能在完全不惊动 Nacos 远程大本营、不干扰其他 9 台机器的前提下,让这台特殊的机器单独走本地特异化路由!