摘要:从一次"微服务找不到彼此"的调用失败出发,深度剖析Nacos服务注册与发现、配置中心、健康检查的核心原理。通过AP/CP模式的权衡选择、临时实例与持久化实例的区别、以及配置热更新的推送机制,揭秘为什么Nacos能替代Eureka+Config+Bus三个组件、双注册中心如何保证高可用、以及Nacos集群的部署架构。配合时序图展示服务调用流程,给出生产环境的最佳实践。
💥 翻车现场
周一早上,哈吉米部署了第一个微服务应用。
架构:
订单服务 → 调用 → 库存服务
代码:
// 订单服务
@FeignClient(name = "stock-service", url = "http://192.168.1.20:8081")
public interface StockClient {
@GetMapping("/stock/decrease")
Result decrease(@RequestParam Long productId);
}
@Service
public class OrderService {
@Autowired
private StockClient stockClient;
public void createOrder(Order order) {
// 调用库存服务
stockClient.decrease(order.getProductId());
}
}
测试:运行正常 ✅
上线第一天:库存服务扩容到3台
库存服务:
192.168.1.20:8081
192.168.1.21:8081 ← 新增
192.168.1.22:8081 ← 新增
问题:
订单服务还是调用:http://192.168.1.20:8081
→ 其他2台机器空闲
→ 192.168.1.20压力大,经常超时
哈吉米:"卧槽,url写死了,无法负载均衡!而且如果192.168.1.20挂了,整个订单服务都不可用!"
技术总监:"用Nacos做服务注册与发现,动态感知服务实例。"
哈吉米:"Nacos是啥?"
南北绿豆和阿西噶阿西来了。
南北绿豆:"Nacos = Naming + Configuration Service,服务注册发现 + 配置中心。"
阿西噶阿西:"来,我给你讲讲Nacos的核心功能。"
🤔 Nacos是什么?解决什么问题?
传统方式的问题
问题1:服务地址写死
@FeignClient(url = "http://192.168.1.20:8081") // 写死
问题:
- ❌ 服务扩容,无法自动发现新实例
- ❌ 服务下线,仍然调用(失败)
- ❌ 无法负载均衡
问题2:配置文件分散
每个服务都有自己的application.yml:
- 订单服务:application.yml(数据库配置、Redis配置...)
- 库存服务:application.yml
- 支付服务:application.yml
问题:
- ❌ 配置重复(每个服务都要配Redis地址)
- ❌ 修改配置要重启(改Redis地址 → 重启所有服务)
- ❌ 配置不统一(订单服务用Redis-1,库存服务用Redis-2)
Nacos的解决方案
功能1:服务注册与发现
服务启动时:
库存服务启动 → 注册到Nacos(IP:192.168.1.20:8081)
库存服务扩容 → 新实例注册到Nacos(IP:192.168.1.21:8081)
订单服务调用:
订单服务 → 从Nacos获取库存服务的实例列表
→ [192.168.1.20:8081, 192.168.1.21:8081, 192.168.1.22:8081]
→ 负载均衡选择一个调用 ✅
功能2:配置中心
配置统一存储在Nacos:
- 数据库配置(MySQL地址、用户名、密码)
- Redis配置
- MQ配置
- 业务参数(限流阈值、开关)
修改配置:
Nacos控制台修改 → 实时推送给所有服务 → 服务自动刷新 ✅
阿西噶阿西:"Nacos = Eureka(服务发现)+ Config(配置中心)+ Bus(配置推送),一个组件顶三个!"
🎯 核心功能1:服务注册与发现
服务注册流程
// 1. 引入Nacos依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
// 2. 配置Nacos地址
spring:
application:
name: stock-service # 服务名
cloud:
nacos:
discovery:
server-addr: 192.168.1.100:8848 # Nacos地址
// 3. 启动类加注解
@SpringBootApplication
@EnableDiscoveryClient // 开启服务发现
public class StockServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StockServiceApplication.class, args);
}
}
// 4. 服务启动时,自动注册到Nacos
注册流程图
sequenceDiagram
participant StockSvc as 库存服务
participant Nacos as Nacos Server
participant OrderSvc as 订单服务
StockSvc->>StockSvc: 1. 应用启动
StockSvc->>Nacos: 2. 注册实例<br/>name=stock-service<br/>ip=192.168.1.20, port=8081
Nacos->>Nacos: 3. 存储实例信息
Nacos->>StockSvc: 4. 注册成功
loop 心跳保活(每5秒)
StockSvc->>Nacos: 5. 发送心跳
Nacos->>StockSvc: 6. 心跳响应
end
OrderSvc->>Nacos: 7. 查询stock-service的实例列表
Nacos->>OrderSvc: 8. 返回:[192.168.1.20:8081, 192.168.1.21:8081]
OrderSvc->>OrderSvc: 9. 负载均衡选择一个实例
OrderSvc->>StockSvc: 10. 调用192.168.1.20:8081
服务发现代码
// 订单服务
@FeignClient(name = "stock-service") // 不用写url,从Nacos获取
public interface StockClient {
@GetMapping("/stock/decrease")
Result decrease(@RequestParam Long productId);
}
@Service
public class OrderService {
@Autowired
private StockClient stockClient;
public void createOrder(Order order) {
// Feign自动从Nacos获取stock-service的实例
// 自动负载均衡(默认轮询)
stockClient.decrease(order.getProductId());
}
}
负载均衡策略
# 配置负载均衡策略
stock-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 随机
# 或者
stock-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # 轮询(默认)
策略对比:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 轮询(默认) | 依次调用每个实例 | 实例性能相同 |
| 随机 | 随机选择实例 | 通用 |
| 权重 | 按权重分配流量 | 实例性能不同 |
| 最少连接 | 选择连接数最少的实例 | 长连接场景 |
🎯 核心功能2:配置中心
配置中心的价值
问题场景:
修改Redis地址(192.168.1.10 → 192.168.1.20)
传统方式:
1. 修改订单服务的application.yml
2. 修改库存服务的application.yml
3. 修改支付服务的application.yml
4. 重启订单服务
5. 重启库存服务
6. 重启支付服务
问题:
- ❌ 每个服务都要改
- ❌ 必须重启(有停机时间)
Nacos方式:
1. Nacos控制台修改Redis配置
2. Nacos推送配置给所有服务
3. 服务自动刷新配置(不重启)✅
耗时:10秒
停机时间:0
配置中心代码
// 1. 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
// 2. 配置Nacos地址(bootstrap.yml)
spring:
application:
name: order-service
cloud:
nacos:
config:
server-addr: 192.168.1.100:8848
file-extension: yaml
namespace: dev # 环境隔离
// 3. Nacos控制台创建配置
Data ID: order-service.yaml
Group: DEFAULT_GROUP
配置内容:
spring:
redis:
host: 192.168.1.10
port: 6379
// 4. 代码中使用
@Configuration
@RefreshScope // 关键:支持配置热更新
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Bean
public RedisTemplate redisTemplate() {
// 使用配置
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(
new LettuceConnectionFactory(redisHost, redisPort)
);
return template;
}
}
配置热更新流程
sequenceDiagram
participant Admin as 运维
participant Nacos as Nacos Server
participant OrderSvc as 订单服务
participant StockSvc as 库存服务
Admin->>Nacos: 1. 修改Redis配置<br/>host: 192.168.1.10 → 192.168.1.20
Nacos->>Nacos: 2. 保存配置,版本+1
par 推送配置
Nacos->>OrderSvc: 3. 推送新配置(长轮询)
Nacos->>StockSvc: 4. 推送新配置
end
OrderSvc->>OrderSvc: 5. 刷新@RefreshScope的Bean
Note over OrderSvc: redisHost: 192.168.1.10 → 192.168.1.20<br/>不重启 ✅
StockSvc->>StockSvc: 6. 刷新@RefreshScope的Bean
Note over StockSvc: 配置已更新
Note over OrderSvc,StockSvc: 全部服务配置已更新<br/>无需重启
南北绿豆:"看到了吗?配置修改后,Nacos自动推送给所有服务,服务热更新,不用重启!"
🎯 核心功能3:健康检查
健康检查机制
Nacos的健康检查有2种:
模式1:客户端主动上报(临时实例)
服务启动 → 注册到Nacos → 每5秒发送心跳
Nacos判断:
- 15秒内收到心跳 → 健康 ✅
- 15秒未收到心跳 → 不健康 ❌(从服务列表移除)
特点:
- 客户端主动上报
- 服务挂了,停止心跳,Nacos自动摘除
- 适合:临时服务(容器、微服务)
模式2:服务端主动探测(持久化实例)
Nacos主动探测:
每20秒向服务发送HTTP/TCP请求
判断:
- 响应正常 → 健康 ✅
- 响应超时/失败 → 不健康 ❌(标记为下线,但不删除)
特点:
- 服务端主动探测
- 即使服务挂了,实例信息仍保留
- 适合:持久化服务(传统部署)
临时实例 vs 持久化实例
配置:
spring:
cloud:
nacos:
discovery:
ephemeral: true # 临时实例(默认)
# ephemeral: false # 持久化实例
对比:
| 特性 | 临时实例(AP) | 持久化实例(CP) |
|---|---|---|
| 健康检查 | 客户端主动上报心跳 | 服务端主动探测 |
| 服务挂了 | 从列表移除 | 标记不健康,保留实例 |
| 适用场景 | 微服务、容器 | 传统部署、虚拟机 |
| 一致性模型 | AP(可用性优先) | CP(一致性优先) |
南北绿豆:"微服务推荐用临时实例,容器随时可能销毁重建,临时实例更合适。"
🎯 AP模式 vs CP模式
什么是AP和CP?
CAP理论:
C(Consistency):一致性
A(Availability):可用性
P(Partition Tolerance):分区容错性
定理:最多只能同时满足2个
分布式系统必须P,所以只能在C和A之间选择:
- CP:牺牲可用性,保证一致性(Zookeeper、Consul)
- AP:牺牲一致性,保证可用性(Eureka、Nacos临时实例)
Nacos的CP模式
场景:持久化实例
Nacos集群(3个节点):
节点1、节点2、节点3
注册服务:
1. 客户端向节点1注册
2. 节点1写入数据,同步给节点2、节点3
3. 超过半数确认(2个) → 注册成功
4. 返回客户端
网络分区:
节点1、节点2在分区A(2个节点)
节点3在分区B(1个节点)
结果:
- 分区A有2个节点(过半数),可以处理注册请求 ✅
- 分区B只有1个节点(未过半数),拒绝注册请求 ❌
特点:
- ✅ 强一致性(数据不会丢)
- ❌ 分区B不可用(牺牲可用性)
Nacos的AP模式
场景:临时实例
Nacos集群(3个节点):
节点1、节点2、节点3
注册服务:
1. 客户端向节点1注册
2. 节点1立即返回成功(不等待同步)
3. 后台异步同步给节点2、节点3
网络分区:
节点1、节点2在分区A
节点3在分区B
结果:
- 分区A可以处理注册 ✅
- 分区B也可以处理注册 ✅(但数据可能不一致)
特点:
- ✅ 高可用(所有分区都可用)
- ❌ 弱一致性(可能短暂不一致,最终一致)
如何选择?
| 场景 | 推荐模式 | 原因 |
|---|---|---|
| 微服务注册 | AP | 可用性更重要,短暂不一致可接受 |
| 配置中心 | CP | 一致性更重要,配置错误会出大问题 |
| 分布式锁 | CP | 必须强一致 |
| 服务发现 | AP | 服务列表短暂不一致可接受 |
阿西噶阿西:"Nacos的设计很巧妙:临时实例用AP,持久化实例用CP,根据场景自动选择。"
🎯 Nacos的注册表结构
服务实例的存储
Nacos的注册表(内存Map):
Map<String, Service> serviceMap = {
"stock-service": {
name: "stock-service",
instances: [
{
ip: "192.168.1.20",
port: 8081,
healthy: true,
weight: 1.0,
metadata: {...}
},
{
ip: "192.168.1.21",
port: 8081,
healthy: true,
weight: 1.0
}
]
},
"order-service": {
...
}
}
实例字段:
| 字段 | 含义 | 示例 |
|---|---|---|
| ip | 服务IP | 192.168.1.20 |
| port | 服务端口 | 8081 |
| healthy | 是否健康 | true/false |
| weight | 权重(负载均衡用) | 1.0 |
| metadata | 元数据(自定义信息) | {version: "1.0"} |
🎯 配置中心的3个核心特性
特性1:配置分层
Nacos配置的3个维度:
1. Namespace(命名空间):环境隔离
- dev(开发环境)
- test(测试环境)
- prod(生产环境)
2. Group(分组):项目隔离
- DEFAULT_GROUP(默认)
- ORDER_GROUP(订单项目)
- STOCK_GROUP(库存项目)
3. Data ID(配置文件):具体配置
- order-service.yaml
- common-redis.yaml
- common-mysql.yaml
层级关系:
Namespace: prod
├─ Group: DEFAULT_GROUP
│ ├─ order-service.yaml
│ └─ stock-service.yaml
└─ Group: COMMON_GROUP
├─ redis.yaml
└─ mysql.yaml
特性2:配置热更新
// 方法1:@RefreshScope(推荐)
@Configuration
@RefreshScope // 配置变化时,自动刷新这个Bean
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Bean
public RedisTemplate redisTemplate() {
// 配置变化时,这个Bean会被重建
return new RedisTemplate();
}
}
// 方法2:@NacosValue(监听单个配置)
@Component
public class LimitConfig {
@NacosValue(value = "${rate.limit.qps}", autoRefreshed = true)
private int qps; // 配置变化时,自动更新
public int getQps() {
return qps;
}
}
// 方法3:监听器(自定义处理)
@Component
public class ConfigListener {
@NacosConfigListener(dataId = "order-service.yaml")
public void onConfigChange(String newConfig) {
System.out.println("配置变化:" + newConfig);
// 自定义处理逻辑
}
}
特性3:配置版本管理
Nacos配置历史:
版本1(2024-10-01 10:00):
spring.redis.host=192.168.1.10
版本2(2024-10-05 14:30):
spring.redis.host=192.168.1.20
版本3(2024-10-07 09:15):
spring.redis.host=192.168.1.30
功能:
- 查看历史版本
- 对比版本差异
- 回滚到历史版本
控制台操作:
配置管理 → 历史版本 → 选择版本2 → 回滚
→ 所有服务自动回滚到版本2的配置
🎯 Nacos集群架构
集群部署
Nacos集群(3个节点,推荐奇数):
节点1:192.168.1.101:8848
节点2:192.168.1.102:8848
节点3:192.168.1.103:8848
客户端配置:
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848
特点:
- 节点之间互相同步数据
- 客户端随机连接一个节点
- 某个节点挂了,客户端自动切换到其他节点
数据同步机制
临时实例(AP模式):
Distro协议(Nacos自研):
节点分片:
- 节点1负责:stock-service
- 节点2负责:order-service
- 节点3负责:payment-service
注册流程:
1. stock-service注册到节点1(随机选择)
2. 节点1存储实例信息
3. 节点1异步同步给节点2、节点3
4. 最终一致
持久化实例(CP模式):
Raft协议:
Leader节点:节点1
Follower节点:节点2、节点3
注册流程:
1. 客户端注册到节点1(Leader)
2. 节点1写入日志,复制给节点2、节点3
3. 收到多数确认(2个) → 提交
4. 返回客户端
🎓 面试标准答案
题目:Nacos有哪些核心功能?
答案:
3大核心功能:
1. 服务注册与发现
- 服务启动时注册到Nacos
- 其他服务从Nacos获取实例列表
- 自动负载均衡
- 健康检查(心跳或探测)
2. 配置中心
- 统一管理配置
- 配置热更新(不重启)
- 版本管理(回滚)
- 环境隔离(Namespace)
3. 健康检查
- 临时实例:客户端心跳
- 持久化实例:服务端探测
- 不健康实例自动摘除
AP vs CP:
- 临时实例:AP模式(Distro协议)
- 持久化实例:CP模式(Raft协议)
优势:
- 一个组件顶三个(Eureka + Config + Bus)
- 国产,中文文档友好
- 功能丰富(权重、元数据、保护阈值)
题目:Nacos和Eureka的区别?
答案:
| 特性 | Eureka | Nacos |
|---|---|---|
| 一致性模型 | AP | AP + CP(可选) |
| 健康检查 | 客户端心跳 | 心跳 + 主动探测 |
| 配置中心 | ❌ 需要Config | ✅ 内置 |
| 权重 | ❌ | ✅ |
| 负载均衡 | Ribbon | Ribbon或自带 |
| 维护状态 | ❌ 停止维护 | ✅ 活跃 |
推荐:
- 新项目:Nacos
- 老项目(已用Eureka):可继续用或迁移
🎉 结束语
一周后,哈吉米把微服务架构改造完成。
哈吉米:"用Nacos后,服务扩容、配置修改都太方便了!"
南北绿豆:"对,Nacos的服务注册发现 + 配置中心,是微服务的基础设施。"
阿西噶阿西:"记住:临时实例用AP模式,持久化实例用CP模式,根据场景选择。"
哈吉米:"还有配置热更新,改配置不用重启服务,太爽了!"
南北绿豆:"对,理解了Nacos,就理解了微服务治理的核心!"
记忆口诀:
Nacos服务注册发现,配置中心二合一
临时实例AP模式,持久实例CP模式
心跳保活自动摘除,配置热更新不重启
Namespace环境隔离,Group项目隔离
健康检查保高可用,版本管理可回滚