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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <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
| <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
<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 namespace: public group: DEFAULT_GROUP
|
简要介绍一下 bootstrap 配置文件,,bootstrap
配置文件是一种特殊的配置文件,其核心作用是在应用上下文初始化的早期阶段加载关键配置,为应用的启动和核心组件(如服务注册发现、配置中心等)的初始化提供必要参数。
一般知道,bootstrap
配置文件(bootstrap.properties
或
bootstrap.yaml
)的加载优先级高于普通的
application
配置文件,会在 Spring
应用上下文初始化的最早期被加载,所以一般微服务的时候用的更多
别忘了启动 Nacos
1 2 3 4
| cd nacos/bin
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 中,我们将创建以下几类配置
应用特定配置:每个应用独有的配置,在上面我们创建过了,在这里说一嘴
- nacos-provider.yaml
- nacos-consumer.yaml
共享配置:多个应用共享的配置
扩展配置:应用特定的扩展配置
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; } }
|
接下来我们测试共享的配置的情况,访问我们上面编写的接口,测试共享配置,验证两个服务是否显示相同的共享配置值:
这就是共享配置,所有服务都会配置上这个配置,当然,这个配置也可以被热更新,你可以试试
我们继续来测试一下扩展配置,因为这两个的思路基本一致,所以放在一起了
扩展配置允许应用加载多个配置文件,并按优先级覆盖,在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: 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: 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前缀的配置。
然后测试扩展配置
启动nacos-provider和nacos-consumer服务
访问以下URL测试扩展配置:
验证两个服务是否分别显示各自的扩展配置值
Nacos 配置优先级文件
Nacos配置加载优先级(从高到低):
- 应用特定配置:{spring.application.name}-{profile}.{file-extension}
- 应用默认配置:{spring.application.name}.{file-extension}
- 扩展配置:extension-configs(按照配置顺序,数组中靠后的优先级更高)
- 共享配置: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; } }
|
测试配置优先级
启动nacos-provider服务
访问 http://localhost:8087/priority/test
image-20250722173523081
验证显示的值是否为”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
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;
@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 { 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; }
@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> <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-discovery</artifactId> </dependency> </dependencies>
|
配置文件配置,服务消费者和提供者是差不多的,都需要有这个配置,剩下的就是你自己编辑了
1 2 3 4 5 6 7 8 9
| spring.application.name=nacos-consumer
server.port=8088
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
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
|
@RestController @RequestMapping("/provider") public class ProviderController {
@Value("${server.port}") private String serverPort;
@GetMapping("/hello/{name}") public String hello(@PathVariable String name) { return "你好," + name + "!这是来自端口号为 " + serverPort + " 的服务提供者的响应"; }
@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
|
@RestController @RequestMapping("/consumer") public class ConsumerController {
@Autowired private RestTemplate restTemplate;
@Autowired private DiscoveryClient discoveryClient;
private static final String SERVICE_NAME = "nacos-provider";
@GetMapping("/hello/{name}") public String hello(@PathVariable String name) { return restTemplate.getForObject("http://" + SERVICE_NAME + "/provider/hello/" + name, String.class); }
@GetMapping("/provider-info") public String getProviderInfo() { return restTemplate.getForObject("http://" + SERVICE_NAME + "/provider/info", String.class); }
@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
注册中心的简单实例,之后我们的各种演示都会基于这个基础上进行