Nacos安装
Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
官网:https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html
安装:
- 下载安装包【2.4.3】
- 启动命令:
- Windows:startup.cmd -m standalone
- Linux/macOS:bash startup.sh -m standalone
-m standalone
-m
表示运行模式(mode)。standalone
代表单机模式,即非集群模式(适合本地开发或测试环境)。
本地访问:http://localhost:8848/nacos
注册中心
服务注册
配置web项目
在product 和 order模块需要引入web依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
配置服务器名字和端口号
# 服务名字 spring.application.name=service-order # 指定服务的端口 server.port=8000 spring.application.name=service-product server.port=9000
启动类
/* @SpringBootApplication 整合三大关键注解 @Configuration:标识该类为配置类。 @ComponentScan:自动扫描当前包及子包下的组件(如 @Service, @Controller)。 @EnableAutoConfiguration:启用 Spring Boot 的自动配置(如自动配置数据库、Web 服务器等)。 * */ @SpringBootApplication public class ProductMainApplication { public static void main(String[] args) { SpringApplication.run(ProductMainApplication.class, args); } }
引入服务注册与发现依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
这个依赖可以放在公共的service模块
配置Nacos地址
spring.cloud.nacos.server-addr=127.0.0.1:8848
每一个微服务都需要配置这个地址
启动微服务
启动微服务,发现注册成功,如下图所示:

本地实现集群效果
我们可以利用idea的配置,在本地尝试启动多个服务器,来实现集群的效果:
单击左键,选择复制配置
修改名字并且添加一个参数
修改端口号:
--server.port=8003
同时启动,发现示例发生了变化
服务发现
在每一个微服务的启动类上添加服务发现注解
@EnableDiscoveryClient
@SpringBootApplication @EnableDiscoveryClient //开启服务发现功能 public class ProductMainApplication { public static void main(String[] args) { SpringApplication.run(ProductMainApplication.class, args); } }
单元测试:
在pom文件里面添加单元测试的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
添加测试类和测试方法的注解
@SpringBootTest // 表示这是一个Spring Boot测试类 public class DiscoveryTest { @Test // @Test注解表示这是一个测试方法 void nacosServiceDiscoveryTest() throws NacosException { } }
测试服务发现API: DiscoveryClient
@Autowired DiscoveryClient discoveryClient; @Test void discoveryClientTest(){ for (String service : discoveryClient.getServices()) { System.out.println("service = " + service); //获取ip+port List<ServiceInstance> instances = discoveryClient.getInstances(service); for (ServiceInstance instance : instances) { System.out.println("ip:"+instance.getHost()+";"+"port = " + instance.getPort()); } } } /* service = service-order ip:10.201.132.106;port = 8003 ip:10.201.132.106;port = 8002 ip:10.201.132.106;port = 8000 service = service-product ip:10.201.132.106;port = 9000 ip:10.201.132.106;port = 9001 */
测试服务发现API: NacosServiceDiscovery
@Autowired NacosServiceDiscovery nacosServiceDiscovery; @Test // @Test注解表示这是一个测试方法 void nacosServiceDiscoveryTest() throws NacosException { for (String service : nacosServiceDiscovery.getServices()) { System.out.println("service = " + service); List<ServiceInstance> instances = nacosServiceDiscovery.getInstances(service); for (ServiceInstance instance : instances) { System.out.println("ip:"+instance.getHost()+";"+"port = " + instance.getPort()); } } } /* service = service-order ip:10.201.132.106;port = 8003 ip:10.201.132.106;port = 8002 ip:10.201.132.106;port = 8000 service = service-product ip:10.201.132.106;port = 9000 ip:10.201.132.106;port = 9001 */
在未来我们不需要直接使用
NacosServiceDiscovery
和DiscoveryClient
这两个API,因为框架都已经封装好了。我们只需要添加@EnableDiscoveryClient
注解,开启服务发现功能。
远程调用
下单场景


实现远程请求
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
public Order createOrder(Long productId, Long userId) {
// 远程调用商品服务,获取商品信息
Product product = getProductFromRemote(productId);
Order order = new Order();
order.setId(1L);
// 总金额
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("zhangsan");
order.setAddress("beijing");
// 目前只有一个商品
order.setProductList(Arrays.asList(product));
return order;
}
private Product getProductFromRemote(Long productId){
//1、获取到商品服务所在的所有机器IP+port
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
// 这里默认获取第一个实例
ServiceInstance instance = instances.get(0);
//拼接远程URL
String url = "http://"+instance.getHost() +":" +instance.getPort() +"/product/"+productId;
log.info("远程请求:{}",url);
//2、给远程发送请求:Product.class这个参数是告诉RestTemplate,返回的结果需要转换成Product对象
// 如果没有Product.class参数,返回的结果是一个JSON字符串
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
RestTemplate
RestTemplate 是 Spring 提供的一个 同步 HTTP 客户端工具,用于在 Java 应用中发送 HTTP 请求(如 GET、POST、PUT、DELETE)并处理响应。它简化了与 RESTful API 的交互,支持 JSON、XML 等数据格式的解析。
发送 GET 请求(返回 JSON 对象)
RestTemplate restTemplate = new RestTemplate();
String url = "https://api.example.com/users/1";
// 方式1:直接返回字符串(JSON)
String response = restTemplate.getForObject(url, String.class);
// 方式2:映射为 Java 对象
User user = restTemplate.getForObject(url, User.class);
发送 POST 请求(提交 JSON 数据)
User newUser = new User("John", 25);
ResponseEntity<User> response = restTemplate.postForEntity(
"https://api.example.com/users",
newUser, // 请求体
User.class // 响应类型
);
依赖注入
建议通过 @Bean
配置单例 RestTemplate
,而非每次创建新实例。
@Configuration
public class OrderConfig {
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
}
远程调用的效果
访问http://localhost:8000/create?userId=6&productId=8
的远程调用结果:
远程请求:http://10.201.132.106:9000/product/8
远程请求:http://10.201.132.106:9000/product/8
远程请求:http://10.201.132.106:9000/product/8
远程请求:http://10.201.132.106:9001/product/8
如果我们把9000
这个端口的服务停掉,那么远程调用就会去找9001
这个端口。
另外一个问题就是,ServiceInstance instance = instances.get(0);
默认只取第一个实例,所有会导致远程调用总是访问同一个节点。
负载均衡
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
LoadBalancerClient
@Autowired //一定导入 spring-cloud-starter-loadbalancer
LoadBalancerClient loadBalancerClient;
private Product getProductFromRemoteWithLoadBalance(Long productId){
//1、获取到商品服务所在的所有机器IP+port
ServiceInstance choose = loadBalancerClient.choose("service-product");
//远程URL
String url = "http://"+choose.getHost() +":" +choose.getPort() +"/product/"+productId;
log.info("远程请求:{}",url);
//2、给远程发送请求
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
效果:实现一个轮询的负载均衡
远程请求:http://10.201.132.106:9000/product/8
远程请求:http://10.201.132.106:9001/product/8
远程请求:http://10.201.132.106:9002/product/8
远程请求:http://10.201.132.106:9001/product/8
@LoadBalanced
只需要在RestTemplate
的Bean上面加一个@LoadBalanced
注解,就可以实现负载均衡
@Configuration
public class OrderConfig {
@LoadBalanced //注解式负载均衡
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
}
用的时候只需要上传服务名称就可以了:
// 进阶3:基于注解的负载均衡
private Product getProductFromRemoteWithLoadBalanceAnnotation(Long productId){
String url = "http://service-product/product/"+productId;
//2、给远程发送请求; service-product 会被动态替换
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
注册中心宕机,远程调用还能成功么

- RestTemplate根据微服务的名字,从注册中心里面获取微服务访问地址列表
- 这个利用负载均衡算法,从地址列表里面选择一个IP地址
- 向该IP地址发起请求,获取数据
- RestTemplate第二次发起请求时,会先从实例缓存里面获取微服务访问地址列表,然后选择一个IP,发起请求,访问对方服务,获取数据
- 如果注册中心里面,微服务发生变化,注册中心会实时同步跟新RestTemplate的实例缓存。
- 因此,如果调用过,由于有缓存的存在,远程调用还可以成功
- 如果没有调用过,那么如果注册中心宕机,那么远程调用就不会成功
配置中心
基本使用
启动Nacos
引入依赖:公共依赖
<!-- 配置中心--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
配置application.properties
# Nacos 的配置中心地址 spring.cloud.nacos.server-addr=127.0.0.1:8848 # Nacos 的配置文件 spring.config.import=nacos:service-order.properties
在Nacos里面配置data-id(数据集)
首先创建配置
spring.config.import
和Data ID
要一致获取配置:使用
@Value
注解的形式@Value("${order.timeout}") String orderTimeout; @Value("${order.auto-confirm}") String orderAutoConfirm; @GetMapping("/config") public String config(){ return "order.timeout="+orderTimeout+"; " + "order.auto-confirm="+orderAutoConfirm; }
自动刷新配置:如果需要自动刷新配置还需要加入
@RefreshScope
注解。只有这样,我们在Nacos里面修改了配置才能同步的代码里面@RefreshScope//自动刷新 @RestController public class OrderController { @Autowired OrderService orderService; @Value("${order.timeout}") String orderTimeout; @Value("${order.auto-confirm}") String orderAutoConfirm; @GetMapping("/config") public String config(){ return "order.timeout="+orderTimeout+"; " + "order.auto-confirm="+orderAutoConfirm; } }
@ConfigurationProperties
:实现批量配置且自动刷新/** * order.timeout=3050min * order.auto-confirm=7d */ @Component //配置批量绑定在nacos下,可以无需@RefreshScope就能实现自动刷新 // prefix = "order" //表示配置文件中以order开头的属性都会被绑定到这个类中 @ConfigurationProperties(prefix = "order") @Data public class OrderProperties { // order.timeout String timeout; // order.auto-confirm: auto-confirm 中间的 - 会被转换为驼峰命名法 String autoConfirm; }
Nacos的配置相
order.timeout
和order.auto-confirm
会根据配置的规则,自动绑定OrderProperties
的属性。@Autowired OrderProperties orderProperties; @GetMapping("/config") public String config(){ return "order.timeout="+orderProperties.getTimeout()+"; " +"order.auto-confirm="+orderProperties.getAutoConfirm(); }
使用的是不需要
@RefreshScope
,只需要注入OrderProperties
就可以了。如微服务不需要从 Nacos加载任何配置,需要在
application.properties
里面作如下配置:方式一:完全禁用 Nacos 配置功能
# 禁用 Nacos 配置中心 spring.cloud.nacos.config.enabled=false
方式二:禁用 Spring Boot 对
spring.config.import
的强制性检查,允许你不声明任何 Nacos 配置导入,同时不会报错。这是一种“掩耳盗铃”式的解决方案,它不会真正禁用 Nacos Config 模块,只是跳过了启动时的验证。# 禁用 Nacos 配置中心的导入检查 spring.cloud.nacos.config.import-check.enabled=false
Description: No spring.config.import property has been defined Action: Add a spring.config.import=nacos: property to your configuration. If configuration is not required add spring.config.import=optional:nacos: instead. To disable this check, set spring.cloud.nacos.config.import-check.enabled=false.
- 这个提示表明你的 Spring Boot 应用尝试集成 Nacos 配置中心,但未正确配置
spring.config.import
属性来指定从 Nacos 加载配置 - 从 Spring Cloud 2020.x(如 2020.0.3)开始,必须显式声明
spring.config.import
才能从 Nacos 加载配置(旧版本是自动激活的)。
配置监听
假如我们有一个需求:当配置发生变化的时候,需要发邮件通知相关人员。这个时候就需要我们去监听配置。
@EnableDiscoveryClient //开启服务发现功能
@SpringBootApplication
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class, args);
}
//1、项目启动就监听配置文件变化
//2、发生变化后拿到变化值
//3、发送邮件
@Bean // @Bean将返回值注册为 Spring 容器管理的 Bean。
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){
return args -> {
ConfigService configService = nacosConfigManager.getConfigService();
configService.addListener("service-order.properties",
"DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
return Executors.newFixedThreadPool(4);
}
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("变化的配置信息:["+configInfo+"]");
System.out.println("邮件通知...");
}
});
System.out.println("=========");
};
}
}
ApplicationRunner
设计目标:在 Spring Boot 应用完全启动后(即 ApplicationContext
已就绪,所有 Bean 初始化完成),执行一些初始化逻辑或后置启动任务。
@FunctionalInterface
public interface ApplicationRunner extends Runner {
void run(ApplicationArguments args) throws Exception;
}
- 实现
run(ApplicationArguments args)
方法,Spring Boot 会在启动完成后自动调用它。 - 适合执行一次性任务(如注册监听器、加载初始数据、连接外部服务等)。
- 与
CommandLineRunner
类似,但ApplicationRunner
提供对参数更结构化的解析(ApplicationArguments
封装了启动参数)。
@Bean
功能:将方法的返回值(这里是 ApplicationRunner
实例)注册为 Spring 容器管理的 Bean,由 Spring 统一管理生命周期和依赖注入。
关键点:
- 方法名(如
applicationRunner
)默认作为 Bean 的名称。 - 方法参数(如
NacosConfigManager
)会被 Spring 自动注入(前提是NacosConfigManager
本身已是 Bean)。
Lambda 表达式
return args -> {
// 这里是Lambda的方法体
ConfigService configService = nacosConfigManager.getConfigService();
configService.addListener(...);
System.out.println("=========");
};
ApplicationRunner
的 run
方法返回 void
。Lambda 表达式对应的是 ApplicationRunner
接口的 run
方法,而该方法的返回类型是 void
,所以方法体内不需要(也不能)返回值。
等效于:
return new ApplicationRunner() {
@Override
public void run(ApplicationArguments args) {
ConfigService configService = nacosConfigManager.getConfigService();
configService.addListener(...);
System.out.println("=========");
}
};
addListener()
参数说明
ConfigService.addListener(String dataId, String group, Listener listener)
dataId
:要监听的配置文件名(如service-order.properties
)group
:配置文件所属的分组(默认DEFAULT_GROUP
)listener
回调对象:定义配置变更时的处理逻辑(如更新本地缓存、发送告警等)
listener
getExecutor()
功能:返回一个 线程池,用于指定 receiveConfigInfo
回调方法的执行线程。
@Override
public Executor getExecutor() {
return Executors.newFixedThreadPool(4); // 创建固定大小为4的线程池
}
为什么需要为 Nacos 的配置监听器(Listener
)指定线程池?
假设你直接这样写(不指定线程池):
configService.addListener("service-order.properties", "DEFAULT_GROUP", new Listener() { @Override public void receiveConfigInfo(String configInfo) { System.out.println("最新的配置内容:" + configInfo); } });
当 Nacos 服务端推送配置变更时,
receiveConfigInfo
方法会在 Nacos 客户端的网络通信线程中直接执行。如果你的回调逻辑很耗时(比如发邮件、写数据库),会阻塞网络线程,导致Nacos后续配置更新无法及时处理。
示意图:
Nacos服务器 --推送通知--> Nacos客户端网络线程 --直接调用--> receiveConfigInfo() | ↓ (你的耗时逻辑:发邮件/写DB) | ↓ 网络线程被卡住,Nacos服务器无法更新通知
线程池的作用:解耦与异步
通过
getExecutor()
指定线程池后,工作流程变成:Nacos 客户端收到配置变更通知(网络线程)。
将回调任务(receiveConfigInfo)提交到你的线程池,立即释放网络线程。
你的业务逻辑在独立的线程池中异步执行,不影响后续配置更新。
Nacos服务器 --推送通知--> Nacos客户端网络线程 --提交任务--> 线程池 --异步执行--> receiveConfigInfo() | ↓ (耗时逻辑并行处理)
receiveConfigInfo(String configInfo)
功能:当监听的配置发生变化时,Nacos 会调用此方法,并传入最新的配置内容。
参数 configInfo
:变更后的完整配置内容(字符串格式,如 key1=value1\nkey2=value2
)。
监听器的完整工作流程
- 注册监听器:通过
configService.addListener(dataId, group, listener)
注册。 - Nacos 服务端推送变更:当有人在 Nacos 控制台修改配置时,服务端会主动通知客户端。
- 触发回调:客户端调用
receiveConfigInfo
,并传入新配置内容。 - 线程池处理:回调方法会在
getExecutor()
返回的线程池中执行。
线程池优化
- 问题:每次配置变更都会创建新线程池(
Executors.newFixedThreadPool(4)
),可能导致资源泄露。 - 解决:改用单例线程池:
private static final Executor executor = Executors.newFixedThreadPool(4);
@Override
public Executor getExecutor() {
return executor; // 复用线程池
}
多配置下的配置优先级
Nacos中的数据集和application.properties有相同的配置项,哪个生效?
- Nacos中的数据集的配置生效
配置的优先级规则:先导入优先,外部优先

高优先级的配置会和低优先级的配置合并,生成一份有效的配置,存入环境变量当中。如果要使用配置的时候,从环境变量里面去。
合并的原则就是:先导入优先,外部优先
外部游戏体现在:Nacos的配置优先于
application.properties
的配置先导入优先体现在:下面导入了
service-order.properties
和common.properties
两个properties,如果两个properties有相同配置项,那么service-order.properties
的配置项优先,因为其先导入。spring.config.import=nacos:service-order.properties,nacos:common.properties
配置隔离
多环境

SpringBoot需要定义多个环境,不同环境的配置不一样。比如开发环境和生产环境的数据库就肯定不一样。
Nacos有Namespace,不同的环境对应不同的Namespace。
Namespace里面又可以分成不同的Group,不同的Group对应不同微服务的配置。比如order模块和product模块的配置就要分成不同的Group
同一个微服务可能有多个配置文件,不同的配置文件对应不同的Data-id。
Nacos操作
创建命名空间


创建Group和Data-id




我们可以通过克隆,把刚才的配置复制到其他开发环境中(命名空间),然后修改值就可以了


代码操作
基本操作
server:
port: 8000
spring:
application:
name: service-order
cloud:
nacos:
server-addr: 127.0.0.1:8848 # Nacos 的地址
config:
namespace: dev # 没有这个配置默认会去public找。指定命名空间
group: order # 设置配置文件的分组为order:全局默认分组
config:
import: # 引入配置
# 这里的group=order表示配置文件的分组为order
# 如果我们想引入其他分组的配置文件,可以在这里修改,比如 group=common
# 单个配置文件的专属分组
- nacos:common.properties?group=order
- nacos:database.properties?group=order
不同环境配置不一样怎么处理
如果不同开发环境,配置的数量和配置的名称都不一样,要如何处理?
server:
port: 8000
spring:
# 设置活动的配置文件
profiles:
# 表示当前活动的配置文件为pro,也就是激活了pro配置
# 只有激活了pro配置,spring.profiles.active.on-profile的值才会是pro
active: pro
application:
name: service-order
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
# namespace这里写成了${spring.profiles.active:public},表示
# 如果spring.profiles.active有值,则使用该值作为命名空间
# 如果没有设置spring.profiles.active的值,则默认使用public命名空间
namespace: ${spring.profiles.active:public}
group: order # 设置配置文件的分组为order:全局默认分组
---
spring:
config:
import:
# 这里的group=order表示配置文件的分组为order
# 如果我们想引入其他分组的配置文件,可以在这里修改,比如 group=common
# 单个配置文件的专属分组
- nacos:common.properties?group=order
- nacos:database.properties?group=order
# dev环境加载common.properties和database.properties
activate:
on-profile: dev
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- nacos:haha.properties?group=order
# test环境加载common.properties、database.properties和haha.properties
activate:
on-profile: test
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- nacos:hehe.properties?group=order
# pro环境加载common.properties、database.properties和hehe.properties
activate:
on-profile: pro
在 YAML 配置中,---
是 文档分隔符(YAML 的多文档特性),它的作用是将单个文件内的配置按逻辑分段,通常用于隔离不同环境的配置或功能模块。
---
的核心作用
场景 | 用途 | 示例 |
---|---|---|
多环境配置隔离 | 在同一个 application.yml 中定义不同 Profile 的配置 |
dev /test /prod 环境分离 |
配置模块化 | 将数据库、缓存等不同组件的配置分开 | 提升可读性 |
覆盖默认值 | 后定义的配置会覆盖前面的同名配置 | 优先级控制 |
删除 ---
:所有配置会合并成一个文档,导致:
- 环境隔离失效:
spring.config.activate.on-profile
可能无法正确绑定到对应环境。 - 配置覆盖混乱:同名属性(如
server.port
)可能被意外覆盖。
问题核心风险
# application.yml
spring:
profiles:
active: dev # 默认开发环境(危险!可能忘记修改)
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- nacos:hehe.properties?group=order
# pro环境加载common.properties、database.properties和hehe.properties
activate:
on-profile: pro
若部署时未显式指定 --spring.profiles.active=prod
,将错误使用开发环境配置。
部署时强制指定环境:java -jar app.jar --spring.profiles.active=prod
多文件方式
文件结构规划
src/main/resources/
├── application.yml # 公共基础配置
├── application-dev.yml # 开发环境配置
├── application-test.yml # 测试环境配置
└── application-prod.yml # 生产环境配置
公共基础配置
server:
port: 8000
spring:
application:
name: service-order
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
group: order
import-check:
enabled: true # 生产环境建议开启检查
# 注意:此处不设置 profiles.active!
开发环境配置
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
cloud:
nacos:
config:
namespace: dev # 开发环境的命名空间
测试环境配置
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- optional:nacos:haha.properties?group=order # 测试特有配置
cloud:
nacos:
config:
namespace: test # 测试环境的命名空间
生产环境配置
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- optional:nacos:hehe.properties?group=order # 生产特有配置
cloud:
nacos:
config:
namespace: pro # 生产环境的命名空间
Idea启动


这个prod
是application-prod.yml
文件的后缀
如果有一文件名是application-local.yml
,那么Active profiles
就填local
。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com