CC 咖啡猫的工作空间 Coding Space

Nacos 深入原理

服务注册发现 + 配置管理平台。阿里的开源项目,Spring Cloud 中替代 Eureka(注册中心)+ Spring Cloud Config(配置中心)的更优选择。一套平台搞定两件事。


1. 两个核心功能

1.1 服务注册发现

服务提供者 Provider
    ↓ 注册(把自己的 IP:Port 告诉 Nacos)
    ↓
  Nacos Server(注册中心)
    ↓
服务消费者 Consumer
    ↓ 订阅 + 发现(从 Nacos 获取Provider地址)
    ↓
直接调用 Provider

1.2 配置管理

应用启动 → 从 Nacos 读取配置文件
                    ↓
配置文件变更 → Nacos 推送更新到应用 → 应用无感重新加载配置

2. 服务注册发现原理

2.1 概念解释

概念 说明
实例(Instance) 一个运行中的 Provider 进程
服务(Service) 同一种业务的一组实例集合
集群(Cluster) 实例的逻辑分组(如机房A、机房B)
Service: order-service
  Cluster: Hangzhou
    Instance: 192.168.1.10:8080 (实例1)
    Instance: 192.168.1.11:8080 (实例2)
  Cluster: Beijing
    Instance: 192.168.2.10:8080 (实例3)

2.2 注册流程

// 应用启动时,Spring Cloud 向 Nacos 注册
// 伪代码逻辑
ServiceInstance instance = new ServiceInstance();
instance.setHost("192.168.1.10");
instance.setPort(8080);
instance.setServiceName("order-service");

nacosServer.registerInstance(instance);  // 注册自己

服务提供者停止时,Nacos 有心跳检测机制:

Provider 启动 → 注册到 Nacos
     ↓
Nacos 定时发送心跳(默认 5秒)
     ↓
超过 15 秒没响应 → Nacos 认为实例不健康 → 从列表中摘除
     ↓
超过 30 秒没响应 → 彻底删除实例

2.3 发现流程

// 消费者从 Nacos 获取服务列表
List<ServiceInstance> instances = nacosServer.selectInstances("order-service", true);
// true = 只返回健康实例

Ribbon/Feign 底层调用这个方法,然后配合负载均衡算法(RoundRobin、Random、Weight)选择一个实例调用。

2.4 心跳机制细节

Nacos 1.x 使用 HTTP 长轮询 + 客户端上报心跳:

  1. Provider 每 5 秒主动上报心跳到 Nacos Server
  2. Nacos Server 超过 15 秒没收到心跳,标记为不健康
  3. 超过 30 秒,删除实例

Nacos 2.x 升级为 gRPC 协议,性能更好。


3. 配置中心原理

3.1 配置管理流程

// 应用启动时加载配置
String config = nacosServer.getConfig("order-service.yml", "dev");

// Nacos 控制台修改了配置,发布
//     ↓
// Nacos 主动推送到所有订阅的客户端(HTTP 长轮询,30秒超时)
//     ↓
// 客户端收到变更通知 → 重新拉取最新配置 → 刷新到 Spring 容器

3.2 配置变更推送机制

Nacos 使用 HTTP 长轮询 实现配置变更推送:

客户端 → 发起请求询问"配置变了没"
          ↓
服务端 hold 请求 30 秒
          ↓
30秒内配置没变 → 返回 304(Not Modified)
30秒超时 → 返回 304 → 客户端立刻再次请求(继续等)
          ↓
人为修改配置 → 服务端立即返回最新配置内容

这个机制保证了配置变更的实时性(最快 30ms 感知),又不浪费轮询资源。

3.3 配置的命名空间隔离

命名空间(Namespace)
  └── 分组(Group)
        └── 配置(Data ID)
# Nacos 配置示例
data-id: order-service.yml
group: DEFAULT_GROUP
namespace: production

不同环境用不同 Namespace(dev/test/prod),不同业务用不同 Group。


4. Nacos 与其他组件对比

4.1 注册中心对比:Nacos vs Eureka vs Zookeeper

特性 Nacos Eureka Zookeeper
一致性模型 CP/AP 可切换 AP(只保证最终一致) CP(强一致)
配置管理 支持 不支持 支持(但体验差)
多环境隔离 Namespace 多 Profile 多环境困难
活跃度 阿里背书,非常活跃 Netflix 已停止维护 Apache 维护
CAP 原则 支持两种模式 只支持 AP 只支持 CP

Nacos CP/AP 切换

# Nacos 默认是 AP 模式(临时实例)
# 切换为 CP 模式(持久化实例)
nacos.server.capability.mode=CP

CP 模式下,注册的是持久化实例, Leader 宕机不允许写入,适合 Etcd/Zookeeper 场景。

4.2 配置中心对比:Nacos vs Apollo vs Spring Cloud Config

特性 Nacos Apollo Spring Cloud Config
配置推送 HTTP 长轮询 HTTP 长轮询 Git Hook(需结合 Bus)
实时性 ~30ms ~30ms 分钟级
多环境管理 Namespace 多环境+多集群 Git 分支
权限管理 基础 完整(部门+人)
审计日志 基础 完整
部署复杂度 简单(单节点) 复杂(MySQL+多组件) 简单
规模 万级服务 十万级服务 千级服务

Apollo 的优势:权限体系完整,适合大公司多部门管理;审计日志完善,适合金融等合规场景。

Nacos 的优势:部署简单,功能足够,对 Spring Cloud 集成好。


5. Nacos 集群架构

5.1 集群脑裂问题

Nacos 默认是 AP 模式,通过 Raft 协议选主:

节点A(Leader)  节点B(Follower)  节点C(Follower)

网络分区时:

分区1:节点A + 节点B
分区2:节点C(孤立的 Follower)
        ↓
节点C 如果也能选主 → 出现两个主(脑裂)
        ↓
Raft 的解决方案:节点C 选主失败(得票不足),不对外提供服务

5.2 持久化模式

Nacos 默认使用嵌入式数据库(Derby),集群部署需要配置外部 MySQL:

# cluster.conf 指定集群节点
192.168.1.10:8848
192.168.1.11:8848
192.168.1.12:8848

# application.properties 指定外部数据库
spring.datasource.platform=mysql
db.url=jdbc:mysql://192.168.1.100:3306/nacos_config
db.username=nacos
db.password=nacos

6. 实战配置示例

6.1 Spring Boot 接入 Nacos 配置中心

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
# bootstrap.yml
spring:
  application:
    name: order-service
  cloud:
    nacos:
      config:
        server-addr: 192.168.1.10:8848
        file-extension: yaml        # 配置格式
        namespace: dev             # 命名空间
        group: DEFAULT_GROUP       # 分组
        refresh-enabled: true      # 开启自动刷新
  profiles:
    active: dev                   # 环境

6.2 动态刷新配置

@RestController
@RefreshScope  // 关键注解:支持动态刷新配置
public class OrderController {

    @Value("${order.page-size:10}")
    private int pageSize;

    @GetMapping("/orders")
    public List<Order> getOrders() {
        // pageSize 会随配置变更自动更新
        return orderService.page(pageSize);
    }
}

配置变更后,Nacos 推送通知 → @RefreshScope 重新创建 Bean → 新的配置值生效。

6.3 分布式配置共享

// 共享配置:多个服务共享同一份配置
// 优先级:服务自身配置 > 共享配置
@PropertySource(value = "classpath:shared.properties")

7. 常见问题与排查

7.1 服务已注册但 Discovery 找不到

常见原因:

  1. 实例的 healthCheck 没配置,Nacos 无法判断健康状态 → 配置健康检查
  2. 网络不通(安全组/防火墙)→ 检查网络连通性
  3. 命名空间不一致(不同 Namespace 的服务互相不可见)→ 确认 Namespace

7.2 配置变更后应用没有刷新

排查步骤:

# 1. 确认 Nacos 控制台配置内容正确
# 2. 确认客户端日志开启了 debug
logging.level.com.alibaba.nacos.config=DEBUG

# 3. 看是否有收到变更通知的日志
# "Nacos config received push: ..."

常见原因:

  1. 没有加 @RefreshScope 注解
  2. 注解标在错误的类上(不是标在需要刷新配置的类)
  3. 使用了静态变量(static 字段不受 @RefreshScope 管理)

7.3 客户端无法连接 Nacos Server

# 确认 Nacos Server 健康状态
curl http://192.168.1.10:8848/nacos/v1/console/health

# 返回 {"status":"UP"} 则正常

网络问题:Nacos 需要开放两个端口:

  • 8848:客户端连接/请求 API
  • 9848:集群通信(gRPC,Nacos 2.x)

8. 最佳实践

  1. 生产环境必须用集群模式:单节点 Nacos 挂了,所有服务无法注册和发现
  2. 命名空间隔离环境:dev/test/prod 用不同 Namespace,避免污染
  3. 配置使用 YAML 格式:比 properties 更结构化,支持嵌套
  4. 配置加密:敏感配置(数据库密码、Token)使用 Nacos 的加密功能
  5. 配置灰度发布:先发布到部分实例,确认无误再全量