Nacos 配置中心的实践

Nacos 配置中心的基本实践和配置刷新实践

复习一下 Maven 等来导入 Naocs 相关依赖的内容

首先我们声明项目的版本信息

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
<!--  只声明依赖,不引入依赖 -->
<dependencyManagement>
<dependencies>
<!-- 声明springBoot版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 声明springCloud版本 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 声明 springCloud Alibaba 版本 -->
<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>
————————————————
版权声明:本文为CSDN博主「张维鹏」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a745233700/article/details/122916208

添加 nacos 配置中心的 maven 依赖:

1
2
3
4
5
6
7
8
9
10
11
<!-- Nacos配置中心依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- Spring Cloud Bootstrap 依赖,用于加载bootstrap.yml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

添加如下配置文件,服务消费者也一样

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: nacos-provider
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
# 指定命名空间,默认为public
namespace: public
# 指定配置组,默认为DEFAULT_GROUP
group: DEFAULT_GROUP

简要介绍一下 bootstrap 配置文件,,bootstrap 配置文件是一种特殊的配置文件,其核心作用是在应用上下文初始化的早期阶段加载关键配置,为应用的启动和核心组件(如服务注册发现、配置中心等)的初始化提供必要参数。

一般知道,bootstrap 配置文件(bootstrap.propertiesbootstrap.yaml)的加载优先级高于普通的 application 配置文件,会在 Spring 应用上下文初始化的最早期被加载,所以一般微服务的时候用的更多

别忘了启动 Nacos

1
2
3
4
# 进入Nacos目录
cd nacos/bin
# 启动Nacos服务器(单机模式)
startup.cmd -m standalone

像这样创建配置,Data ID一般都是服务名+文件扩展名,然后往 Nacos 的配置这块中把服务里面最基本的配置写进去就可以,当然你可以全都写进去

image-20250721232622067

配置的填写按照如下形式进行实现

image-20250722232118765

写一个配置的控制器类,方便能查看效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@RequestMapping("/config")
@RefreshScope // 支持配置动态刷新
public class ConfigController {

@Value("${provider.config.name:default-name}")
private String name;

@Value("${provider.config.version:1.0}")
private String version;

@Value("${provider.config.env:local}")
private String env;

@GetMapping("/info")
public Map<String, String> getConfigInfo() {
Map<String, String> configInfo = new HashMap<>();
configInfo.put("name", name);
configInfo.put("version", version);
configInfo.put("env", env);
return configInfo;
}
}

再写一个监听器,监控配置更改的情况,避免出现假更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package edu.software.ergoutree.nacosprovider.listener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class ConfigChangeListener {

private static final Logger logger = LoggerFactory.getLogger(ConfigChangeListener.class);

@EventListener
public void onEnvironmentChangeEvent(EnvironmentChangeEvent event) {
logger.info("配置发生变更,变更的属性: {}", event.getKeys());
}
}

然后试着访问上面我们配置的接口,可以看到是没问题的,我们接下来进行修改

image-20250721232843800

之后来验证动态刷新配置方面的情况,配置的动态刷新是配置中心最核心的功能之一

你直接在spring里面修改和在 Nacos 里面修改都可以,为了体现其配置中心性质))在 Nacos 改

image-20250722165509868

可以发现配置发生了修改,首先是监听器监听到了配置刷新的情况

image-20250722170205972

然后访问接口,可以发现打印出的配置发生了变更,至此Nacos最基本的配置中心和配置刷新的实践我们已经完成了

image-20250722170247995

Nacos 配置中心进行共享和扩展配置的实践

我们将在这里进行扩展配置和共享配置的配置实践,来看看 Nacos 在共享和扩展配置的支持情况

首先修改我们的两个模块的bootstrap.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: nacos-provider
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
namespace: public
group: DEFAULT_GROUP
# 添加共享配置
shared-configs:
- data-id: common-config.yaml
group: DEFAULT_GROUP
refresh: true
# 添加扩展配置
extension-configs:
- data-id: nacos-provider-ext.yaml
group: DEFAULT_GROUP
refresh: true
# 开启自动刷新
refresh-enabled: true

之后,我们在 Nacos 中,我们将创建以下几类配置

  1. 应用特定配置:每个应用独有的配置,在上面我们创建过了,在这里说一嘴

    • nacos-provider.yaml
    • nacos-consumer.yaml
  2. 共享配置:多个应用共享的配置

    • common-config.yaml

      这一块你不用非要看我的这个,这个配置你自己想往上写什么就往上面写什么,之后刷新能看到清晰的变更就可以,其他别的配置也是一样的啊

      image-20250722171118833
  3. 扩展配置:应用特定的扩展配置

    • nacos-provider-ext.yaml

    • nacos-consumer-ext.yaml

创建使用共享配置的控制器,在nacos-provider模块中创建新的控制器类,然后在nacos-consumer模块中也创建相同的控制器。

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
@RestController
@RequestMapping("/shared")
@RefreshScope
public class SharedConfigController {

// 引用共享配置中的值
@Value("${common.database.url:no-url}")
private String databaseUrl;

@Value("${common.redis.host:localhost}")
private String redisHost;

@Value("${common.logging.level.root:INFO}")
private String logLevel;

@Value("${common.thread-pool.core-size:5}")
private Integer threadPoolCoreSize;

@Value("${common.config-version:unknown}")
private String configVersion;

@GetMapping("/info")
public Map<String, Object> getSharedConfigInfo() {
Map<String, Object> configInfo = new HashMap<>();
configInfo.put("databaseUrl", databaseUrl);
configInfo.put("redisHost", redisHost);
configInfo.put("logLevel", logLevel);
configInfo.put("threadPoolCoreSize", threadPoolCoreSize);
configInfo.put("configVersion", configVersion);
return configInfo;
}
}

接下来我们测试共享的配置的情况,访问我们上面编写的接口,测试共享配置,验证两个服务是否显示相同的共享配置值:

  • http://localhost:8087/shared/info (provider)

    image-20250722171545407
  • http://localhost:8088/shared/info (consumer)

    image-20250722171559097

这就是共享配置,所有服务都会配置上这个配置,当然,这个配置也可以被热更新,你可以试试

我们继续来测试一下扩展配置,因为这两个的思路基本一致,所以放在一起了

扩展配置允许应用加载多个配置文件,并按优先级覆盖,在Nacos控制台创建你的模块的扩展配置:

image-20250722172229977
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Provider扩展配置
provider:
ext:
feature-toggle: true
timeout: 5000
retry-times: 3
max-connections: 100

# 业务特定配置
business:
service-mode: standard
batch-size: 50

# 监控配置
monitor:
enabled: true
interval: 60

# 配置版本信息
ext-version: v1.0.0
update-time: 2023-04-01 10:00:00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Consumer扩展配置
consumer:
ext:
feature-toggle: true
timeout: 3000
retry-times: 2
max-connections: 50

# 业务特定配置
business:
service-mode: simple
batch-size: 20

# 监控配置
monitor:
enabled: true
interval: 30

# 配置版本信息
ext-version: v1.0.0
update-time: 2023-04-01 10:00:00

由于你上面的 bootstrap.yml 文件已经被修改,没改的记得改一下

然后创建使用扩展配置的控制器,在nacos-provider模块中创建新的控制器类:

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
@RestController
@RequestMapping("/extension")
@RefreshScope
public class ExtensionConfigController {

// 引用扩展配置中的值
@Value("${provider.ext.feature-toggle:false}")
private boolean featureToggle;

@Value("${provider.ext.timeout:1000}")
private int timeout;

@Value("${provider.ext.retry-times:1}")
private int retryTimes;

@Value("${provider.business.service-mode:basic}")
private String serviceMode;

@Value("${provider.monitor.interval:30}")
private int monitorInterval;

@Value("${provider.ext-version:unknown}")
private String extVersion;

@GetMapping("/info")
public Map<String, Object> getExtensionConfigInfo() {
Map<String, Object> configInfo = new HashMap<>();
configInfo.put("featureToggle", featureToggle);
configInfo.put("timeout", timeout);
configInfo.put("retryTimes", retryTimes);
configInfo.put("serviceMode", serviceMode);
configInfo.put("monitorInterval", monitorInterval);
configInfo.put("extVersion", extVersion);
return configInfo;
}
}

在nacos-consumer模块中创建类似的控制器,但使用consumer前缀的配置。

然后测试扩展配置

  1. 启动nacos-provider和nacos-consumer服务

  2. 访问以下URL测试扩展配置:

    • http://localhost:8087/extension/info (provider)

      image-20250722172538430
    • http://localhost:8088/extension/info (consumer)

      image-20250722172600568
  3. 验证两个服务是否分别显示各自的扩展配置值

Nacos 配置优先级文件

Nacos配置加载优先级(从高到低):

  1. 应用特定配置:{spring.application.name}-{profile}.{file-extension}
  2. 应用默认配置:{spring.application.name}.{file-extension}
  3. 扩展配置:extension-configs(按照配置顺序,数组中靠后的优先级更高)
  4. 共享配置:shared-configs(按照配置顺序,数组中靠后的优先级更高)

在Nacos控制台修改以下配置,nacos-provider.yaml

1
2
3
4
5
# 应用默认配置
overlap:
config:
value: "from-application-default"
source: "application-default"

nacos-provider-ext.yaml也要进行修改,添加重叠配置

1
2
3
4
5
6
7
# 原有扩展配置保持不变...

# 添加重叠配置
overlap:
config:
value: "from-extension"
source: "extension-config"

common-config.yaml也要进行修改,添加重叠配置

1
2
3
4
5
6
7
# 原有共享配置保持不变...

# 添加重叠配置
overlap:
config:
value: "from-shared"
source: "shared-config"

就像这样

image-20250722172915287

在nacos-provider模块中创建新的控制器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package edu.software.ergoutree.nacosprovider.controller;

@RestController
@RequestMapping("/priority")
@RefreshScope
public class ConfigPriorityController {

@Value("${overlap.config.value:default}")
private String overlapValue;

@Value("${overlap.config.source:none}")
private String overlapSource;

@GetMapping("/test")
public Map<String, String> testConfigPriority() {
Map<String, String> result = new HashMap<>();
result.put("overlapValue", overlapValue);
result.put("overlapSource", overlapSource);
return result;
}
}

测试配置优先级

  1. 启动nacos-provider服务

  2. 访问 http://localhost:8087/priority/test

    image-20250722173523081
  3. 验证显示的值是否为”from-application-default”(应用特定配置优先级最高)

你也可以指定配置优先级的顺序

在bootstrap.yml中调整extension-configs的顺序,添加多个扩展配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
nacos:
config:
extension-configs:
- data-id: ext-config-1.yaml
group: DEFAULT_GROUP
refresh: true
- data-id: ext-config-2.yaml
group: DEFAULT_GROUP
refresh: true
- data-id: nacos-provider-ext.yaml
group: DEFAULT_GROUP
refresh: true

在Nacos中创建这些配置文件,并在每个文件中设置不同的overlap.config值,然后测试最终生效的是哪个值。

Nacos 多环境配置

首先还是要先创建多环境的配置文件,在Nacos控制台下创建以下配置,nacos-provider-dev.yaml:

1
2
3
4
5
6
7
8
9
10
# 开发环境配置
provider:
config:
name: nacos-provider-dev
version: 1.0.1
env: dev

env-specific:
debug-mode: true
mock-enabled: true

nacos-provider-test.yaml:

1
2
3
4
5
6
7
8
9
10
# 测试环境配置
provider:
config:
name: nacos-provider-test
version: 1.0.2
env: test

env-specific:
debug-mode: false
mock-enabled: true

nacos-provider-prod.yaml:

1
2
3
4
5
6
7
8
9
10
# 生产环境配置
provider:
config:
name: nacos-provider-prod
version: 1.0.3
env: prod

env-specific:
debug-mode: false
mock-enabled: false

然后需要修改bootstrap.yml支持多环境,在nacos-provider的bootstrap.yml中添加:

1
2
3
4
5
6
7
8
9
spring:
cloud:
nacos:
config:
# 支持多环境配置
prefix: ${spring.application.name}
# 激活的环境配置
profiles:
active: ${spring.profiles.active:dev}

之后创建环境特定控制器用于验证情况

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
package edu.software.ergoutree.nacosprovider.controller;

@RestController
@RequestMapping("/env")
@RefreshScope
public class EnvironmentConfigController {

private final Environment environment;

public EnvironmentConfigController(Environment environment) {
this.environment = environment;
}

@Value("${provider.config.name:default-name}")
private String name;

@Value("${provider.config.env:default}")
private String env;

@Value("${provider.env-specific.debug-mode:false}")
private boolean debugMode;

@Value("${provider.env-specific.mock-enabled:false}")
private boolean mockEnabled;

@GetMapping("/info")
public Map<String, Object> getEnvConfigInfo() {
Map<String, Object> configInfo = new HashMap<>();
configInfo.put("name", name);
configInfo.put("env", env);
configInfo.put("debugMode", debugMode);
configInfo.put("mockEnabled", mockEnabled);
configInfo.put("activeProfile",
environment.getActiveProfiles().length > 0 ?
environment.getActiveProfiles()[0] : "default");
return configInfo;
}
}

测试多环境配置,先使用不同的环境变量启动nacos-provider,当然你上面指定了也是一样的:

1
2
3
4
5
6
7
8
# 开发环境
java -jar -Dspring.profiles.active=dev nacos-provider-0.0.1-SNAPSHOT.jar

# 测试环境
java -jar -Dspring.profiles.active=test nacos-provider-0.0.1-SNAPSHOT.jar --server.port=8089

# 生产环境
java -jar -Dspring.profiles.active=prod nacos-provider-0.0.1-SNAPSHOT.jar --server.port=8090

分别访问不同端口的服务,验证是否加载了对应环境的配置:

  • http://localhost:8087/env/info (dev)
  • http://localhost:8089/env/info (test)
  • http://localhost:8090/env/info (prod)

Nacos 实现环境隔离

Nacos的环境隔离主要是通过 Namespace 和 Group 实现的,Namespace是最粗粒度的隔离,在机器有限的情况下,往往通过Namespace进行环境隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。

Namespace是Nacos实现环境隔离的最外层概念,不同Namespace之间的服务和配置是完全隔离的。

我们新建一个命名空间

image-20250722231649363

在Nacos控制台,切换到”配置管理” -> “配置列表”,在新建的命名空间中创建相应的配置:

image-20250722233512731

然后修改应用配置使用不同的命名空间,创建bootstrap-prod.yml:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
nacos:
config:
namespace: prod
server-addr: 127.0.0.1:8848
file-extension: yaml
group: DEFAULT_GROUP
refresh-enabled: true
discovery:
namespace: prod
server-addr: 127.0.0.1:8848

修改主配置文件bootstrap.yml以引用环境特定配置:

1
2
3
4
5
spring:
application:
name: nacos-provider
profiles:
active: ${NACOS_ENV:prod}

注意,在这里如果你其他配置配置过命名空间的内容,需要划掉,因为会影响服务发现和配置的命名空间的处理导致看不到服务,而且建议命名空间直接配置id

创建命名空间测试控制器,方便观察效果

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
package edu.software.ergoutree.nacosprovider.controller;
@RestController
@RequestMapping("/namespace")
@RefreshScope
public class NamespaceController {

@Value("${provider.config.name:unknown}")
private String name;

@Value("${provider.config.env:unknown}")
private String env;

@Value("${provider.database.url:unknown}")
private String dbUrl;

@Value("${provider.database.username:unknown}")
private String dbUsername;

@Value("${spring.cloud.nacos.config.namespace:unknown}")
private String configNamespace;

@Value("${spring.cloud.nacos.discovery.namespace:unknown}")
private String discoveryNamespace;

@GetMapping("/info")
public Map<String, String> getNamespaceInfo() {
Map<String, String> info = new HashMap<>();
info.put("name", name);
info.put("env", env);
info.put("dbUrl", dbUrl);
info.put("dbUsername", dbUsername);
info.put("configNamespace", configNamespace);
info.put("discoveryNamespace", discoveryNamespace);
return info;
}
}

启动项目,查看效果

image-20250722234010671
image-20250722235330743

我们访问一下我们控制器中的接口,来确认配置拉取是否正常

image-20250722235400314

Nacos 实现集群配置

上面我们实现环境隔离是使用的 Namespace,这次我们使用 Group 进行分组的集群配置

这次对 nacos-consumer 模块进行配置,因为他有多个实例

建立分组,CLUSTER_GROUP,在我们上面的命名空间 cloud-prod 下,因为真正进行这种集群配置一般都是在生产环境,开发环境是很少这样的

新建配置,把我们消费者的服务注册到 Nacos 中,设置好对应的组和命名空间

image-20250723001003456

然后创建你对应实例的配置文件,然后正确指定实例启动的配置文件,以我其中的一个杭州的实例为例子,把服务的命名空间和集群,组等内容都配置好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 应用名称
spring.application.name=nacos-consumer
# 应用端口号
server.port=8088

# Nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 命名空间
spring.cloud.nacos.discovery.namespace=7d31e4b6-cfe4-4045-973b-f91847c06442
# 分组
spring.cloud.nacos.discovery.group=CLUSTER_GROUP
# 集群名称 - 杭州集群
spring.cloud.nacos.discovery.cluster-name=HZ
# 元数据 - 实例标识
spring.cloud.nacos.discovery.metadata.instance-id=consumer-hz-1
spring.cloud.nacos.discovery.metadata.version=1.0.0
spring.cloud.nacos.discovery.metadata.env=cluster
spring.cloud.nacos.discovery.metadata.region=hangzhou
# 权重配置
spring.cloud.nacos.discovery.weight=1
# 日志配置
logging.level.com.alibaba.cloud.nacos=DEBUG
logging.level.edu.software.ergoutree=DEBUG
image-20250723001159091

修改其中的 bootstrap.yml 配置文件,确保配置被正确引用

1
2
3
4
5
spring:
application:
name: nacos-consumer
profiles:
active: ${NACOS_ENV:cluster}

注意,如果你在其他位置配置过命名空间和分组等内容,清除掉,除非你进行了正确的配置引用

写一个健康检查的控制器方便我们进行配置的验证

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package edu.software.ergoutree.nacosconsumer.controller;

@RestController
@RequestMapping("/registration")
public class RegistrationStatusController {

private static final Logger log = LoggerFactory.getLogger(RegistrationStatusController.class);

@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;

@Autowired
private NacosServiceManager nacosServiceManager;

@Value("${spring.application.name}")
private String applicationName;

@Value("${spring.cloud.nacos.discovery.cluster-name:DEFAULT}")
private String clusterName;

@Value("${spring.cloud.nacos.discovery.group:DEFAULT_GROUP}")
private String group;

@Value("${spring.cloud.nacos.discovery.namespace:public}")
private String namespace;

@Value("${server.port}")
private String serverPort;

/**
* 检查当前服务是否已成功注册到Nacos
* @return 注册状态信息
*/
@GetMapping("/status")
public Map<String, Object> checkRegistrationStatus() {
Map<String, Object> result = new HashMap<>();
result.put("applicationName", applicationName);
result.put("clusterName", clusterName);
result.put("group", group);
result.put("namespace", namespace);
result.put("port", serverPort);

try {
// 获取Nacos命名服务
NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());

// 查询当前服务的所有实例
List<Instance> instances = namingService.getAllInstances(applicationName, group);

// 查找当前实例
boolean found = false;
for (Instance instance : instances) {
if (String.valueOf(instance.getPort()).equals(serverPort) &&
clusterName.equals(instance.getClusterName())) {
found = true;
result.put("registered", true);
result.put("healthy", instance.isHealthy());
result.put("weight", instance.getWeight());
result.put("metadata", instance.getMetadata());
result.put("instanceId", instance.getInstanceId());
break;
}
}

if (!found) {
result.put("registered", false);
result.put("message", "实例未在Nacos中注册");
}

// 添加所有实例信息
result.put("totalInstances", instances.size());
result.put("allInstancesInfo", instances);

} catch (NacosException e) {
log.error("查询Nacos注册状态失败", e);
result.put("registered", false);
result.put("error", e.getErrMsg());
}

return result;
}

/**
* 提供一个简单的HTML页面展示注册状态
* @return HTML格式的注册状态
*/
@GetMapping("/status/html")
public String checkRegistrationStatusHtml() {
Map<String, Object> status = checkRegistrationStatus();

StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html><html><head><title>Nacos注册状态</title>");
html.append("<style>body{font-family:Arial;margin:20px;} table{border-collapse:collapse;width:100%;} ");
html.append("th,td{border:1px solid #ddd;padding:8px;text-align:left;} ");
html.append("th{background-color:#f2f2f2;} tr:nth-child(even){background-color:#f9f9f9;} ");
html.append(".success{color:green;} .error{color:red;}</style></head><body>");

html.append("<h1>Nacos服务注册状态</h1>");
html.append("<h2>基本信息</h2>");
html.append("<table>");
html.append("<tr><th>应用名称</th><td>").append(status.get("applicationName")).append("</td></tr>");
html.append("<tr><th>集群名称</th><td>").append(status.get("clusterName")).append("</td></tr>");
html.append("<tr><th>分组</th><td>").append(status.get("group")).append("</td></tr>");
html.append("<tr><th>命名空间</th><td>").append(status.get("namespace")).append("</td></tr>");
html.append("<tr><th>端口</th><td>").append(status.get("port")).append("</td></tr>");

// 注册状态
boolean registered = status.containsKey("registered") ? (Boolean) status.get("registered") : false;
html.append("<tr><th>注册状态</th><td class='").append(registered ? "success" : "error").append("'>");
html.append(registered ? "已注册" : "未注册").append("</td></tr>");

if (registered) {
html.append("<tr><th>健康状态</th><td class='").append((Boolean) status.get("healthy") ? "success" : "error").append("'>");
html.append((Boolean) status.get("healthy") ? "健康" : "不健康").append("</td></tr>");
html.append("<tr><th>权重</th><td>").append(status.get("weight")).append("</td></tr>");
html.append("<tr><th>实例ID</th><td>").append(status.get("instanceId")).append("</td></tr>");
html.append("<tr><th>元数据</th><td>").append(status.get("metadata")).append("</td></tr>");
} else if (status.containsKey("message")) {
html.append("<tr><th>消息</th><td class='error'>").append(status.get("message")).append("</td></tr>");
}

if (status.containsKey("error")) {
html.append("<tr><th>错误</th><td class='error'>").append(status.get("error")).append("</td></tr>");
}

html.append("</table>");

// 所有实例信息
if (status.containsKey("allInstancesInfo")) {
List<Instance> instances = (List<Instance>) status.get("allInstancesInfo");
html.append("<h2>所有实例 (").append(instances.size()).append(")</h2>");

if (!instances.isEmpty()) {
html.append("<table>");
html.append("<tr><th>实例ID</th><th>IP</th><th>端口</th><th>集群</th><th>健康状态</th><th>权重</th><th>元数据</th></tr>");

for (Instance instance : instances) {
html.append("<tr>");
html.append("<td>").append(instance.getInstanceId()).append("</td>");
html.append("<td>").append(instance.getIp()).append("</td>");
html.append("<td>").append(instance.getPort()).append("</td>");
html.append("<td>").append(instance.getClusterName()).append("</td>");
html.append("<td class='").append(instance.isHealthy() ? "success" : "error").append("'>");
html.append(instance.isHealthy() ? "健康" : "不健康").append("</td>");
html.append("<td>").append(instance.getWeight()).append("</td>");
html.append("<td>").append(instance.getMetadata()).append("</td>");
html.append("</tr>");
}

html.append("</table>");
} else {
html.append("<p class='error'>没有找到任何实例</p>");
}
}

html.append("<p><a href='/cluster-consumer/services'>查看服务列表</a> | ");
html.append("<a href='/cluster-consumer/self-info'>查看自身信息</a> | ");
html.append("<a href='/cluster-consumer/health'>健康检查</a></p>");

html.append("</body></html>");
return html.toString();
}
}

我们启动项目,来验证一下配置的配置情况

访问控制器上面定义的端口,确认集群的服务状态

image-20250723002508765
image-20250723002601526

这时候有人就要问了,为什么你上面的那个沈阳实例这里不显示啊,是不是Nacos 配置错了

其实吧,挺抽象的,你 Nacos 再配置错,他其实也不会影响到你的服务启动的配置,可能只会影响到你服务发现和注册这些配置的内容,为什么发现不了其实还是端口被占用,因为 QQ 使用的是 8092端口

接下来访问对应的端口来测试一下配置情况,我这里一个实例展示一个

image-20250723003150923

吐槽一下,我这个写的好想 Erueka

image-20250723003123355
image-20250723003218659

这下更想 Erueka 了,太难绷了

image-20250723003301843

之前的一些实践,方便查阅

Nacos 服务注册与订阅的详细使用

nacos 的安装与配置

https://github.com/alibaba/nacos/releases

下载之后进入到bin文件目录,点击startup.cmd,启动项目:

image-20250710143458140

需要以单机模式启动

1
2
# 单机模式
startup.cmd -m standalone

需要让你输入一些 nacos 的安全配置,用于不同场景的身份验证和权限控制。分别是生成和验证 JWT 的,用于 Nacos Server 集群间通信时的身份验证和服务身份标识,用于标识当前 Nacos Server 的身份(如集群中的唯一 ID)。

image-20250710143842460

在浏览器地址输入http://localhost:8848/nacos/index.html,默认 nacos 账号密码都是 nacos

至此,nacos已经启动成功,我们可以访问地址: http://169.254.27.253:8080/index.html 访问nacos的页面。如果需要登录,默认的用户名和密码都是nacos

image-20250710235629676

打开mysql数据库,进行配置,打开Nacos配置]文件 ${nacos-server.path}/conf/application.properties

image-20250719183639366

因为 nacos 也是一个 Spring 项目,所以说配置文件肯定也是 application.properties,设置正确的数据库平台为MySQL

1
spring.datasource.platform=mysql

配置数据库连接信息

1
2
3
db.url=jdbc:mysql://你的数据库地址:端口/数据库名?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=UTC
db.user=你的数据库用户名
db.password=你的数据库密码

Nacos 在 Spring Cloud 中的服务发现

创建服务提供者模块 nacos-provider,服务提供者是提供接口的微服务,需注册到 Nacos。

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<!-- Spring Web(提供 HTTP 接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos 服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>

配置文件配置,服务消费者和提供者是差不多的,都需要有这个配置,剩下的就是你自己编辑了

1
2
3
4
5
6
7
8
9
# 应用名称,也是注册到Nacos的服务名
spring.application.name=nacos-consumer
# 应用端口号
server.port=8088

# Nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 命名空间,默认为public
spring.cloud.nacos.discovery.namespace=public

写一个控制器,然后对应的主类也要加上服务发现的注解

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
/**
* 服务提供者控制器
* 提供REST API接口供消费者调用
*/
@RestController
@RequestMapping("/provider")
public class ProviderController {

/**
* 注入服务端口号,用于识别是哪个实例提供的服务
*/
@Value("${server.port}")
private String serverPort;

/**
* 提供一个简单的问候服务
* @param name 名称参数
* @return 问候语
*/
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
return "你好," + name + "!这是来自端口号为 " + serverPort + " 的服务提供者的响应";
}

/**
* 提供服务实例信息
* @return 服务信息
*/
@GetMapping("/info")
public String info() {
return "服务提供者实例,端口号:" + serverPort;
}
}

创建服务消费者模块 nacos-consumer,服务消费者通过 Nacos 发现 Provider 并调用其接口。依赖是一样的,写一个消费者的控制器,并且把主启动类也加上对应的服务发现注解

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* 服务消费者控制器
* 通过Nacos服务发现调用服务提供者的接口
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {

/**
* 注入RestTemplate,用于发起HTTP请求
*/
@Autowired
private RestTemplate restTemplate;

/**
* 注入DiscoveryClient,用于获取服务实例信息
*/
@Autowired
private DiscoveryClient discoveryClient;

/**
* 服务提供者的应用名称
*/
private static final String SERVICE_NAME = "nacos-provider";

/**
* 通过负载均衡方式调用服务提供者的hello接口
* @param name 名称参数
* @return 服务提供者返回的结果
*/
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
// 使用服务名称调用,RestTemplate会根据负载均衡策略选择一个实例
return restTemplate.getForObject("http://" + SERVICE_NAME + "/provider/hello/" + name, String.class);
}

/**
* 获取服务提供者的信息
* @return 服务提供者的信息
*/
@GetMapping("/provider-info")
public String getProviderInfo() {
return restTemplate.getForObject("http://" + SERVICE_NAME + "/provider/info", String.class);
}

/**
* 获取所有服务提供者实例的信息
* @return 所有服务提供者实例的信息
*/
@GetMapping("/discovery")
public Object discovery() {
// 获取服务列表
List<String> services = discoveryClient.getServices();
StringBuilder sb = new StringBuilder();
sb.append("所有服务: ").append(services).append("<br/>");

// 获取指定服务的所有实例
List<ServiceInstance> instances = discoveryClient.getInstances(SERVICE_NAME);
sb.append(SERVICE_NAME).append("服务的实例数量: ").append(instances.size()).append("<br/>");

// 输出每个实例的详细信息
for (ServiceInstance instance : instances) {
sb.append("ID: ").append(instance.getInstanceId())
.append(", Host: ").append(instance.getHost())
.append(", Port: ").append(instance.getPort())
.append(", URI: ").append(instance.getUri())
.append("<br/>");
}

return sb.toString();
}
}

别忘了把 Nacos 服务跑起来

image-20250720135020472

先启动服务提供模块,启动后,服务会注册到Nacos,再启动服务消费模块,启动后,服务也会注册到Nacos

image-20250720135335215
image-20250720135359470
image-20250720135444385
image-20250720135550702

可以看到这些模块正常工作,也是可以被正常注册和发现的,然后在这里我们可以进行服务管理、配置管理等操作。

这只是最基本的 Spring Cloud 整合 Nacos 注册中心的简单实例,之后我们的各种演示都会基于这个基础上进行