乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • Spring Cloud Alibaba概述与技术选型
  • Nacos注册中心
  • Nacos配置中心
  • Sentinel流量控制
  • Sentinel降级与熔断
  • Seata分布式事务
  • RocketMQ消息驱动
  • Spring AI Alibaba与AI集成
  • Dubbo RPC服务调用
  • Gateway服务网关
  • GraalVM静态编译
  • 其他组件速览
  • Alibaba最佳实践与面试考点

Nacos 注册中心

定位

Nacos(Dynamic Naming and Configuration Service,动态命名与配置服务)在微服务架构中扮演服务注册中心(Service Registry)的角色。它的核心使命是:

  • 解决服务发现(Service Discovery)问题:在微服务架构下,服务实例的 IP 和端口是动态变化的(弹性伸缩、滚动发布、故障转移)。Nacos 让服务消费者无需硬编码目标地址,只需通过服务名即可调用。
  • 淘汰手动维护 IP:Port 的运维方式:新增实例自动注册,下线实例自动剔除,调用方始终拿到健康实例列表。

对比 Eureka 与 ZooKeeper

维度NacosEurekaZooKeeper
一致性协议AP(Distro)/ CP(Raft)双模式APCP(ZAB)
配置中心内置无需自行实现
健康检查客户端心跳 + 服务端探测客户端心跳临时节点 + Session
多数据中心原生支持原生支持第三方扩展
2.x 通信协议gRPC 双向流HTTPTCP Session

Nacos 的 AP/CP 双模式是核心优势:默认 AP 模式(Distro 协议)保证高可用,服务发现宁可短暂不一致也绝不能不可用;当需要强一致性场景(如配置变更)时,可切换为 CP 模式(Raft 协议)。

一句话讲清楚:Nacos = Eureka 注册中心 + Config 配置中心。本章只讲注册中心,配置中心见下一章。


核心概念

命名空间(Namespace)

命名空间是 Nacos 中最顶层的隔离单位,用于实现多环境完全隔离(开发 / 测试 / 生产)。每个 Namespace 由唯一 UUID 标识。不同 Namespace 下的服务不可互相发现。

飞翔科技的实际使用:dev Namespace(UUID: a1b2c3d4-xxx)用于日常开发联调;prod Namespace(UUID: e5f6a7b8-xxx)用于线上生产环境。

分组(Group)

同一 Namespace 内服务的逻辑分组,默认值为 DEFAULT_GROUP。白歌习惯将支付相关服务划入 PAYMENT_GROUP,订单相关服务划入 ORDER_GROUP,避免跨团队互相干扰。

服务(Service)

一组提供相同功能的实例集合。例如飞翔科技的用户服务部署了 3 个实例,它们在 Nacos 中同属一个 Service,服务名为 user-service。

实例(Instance)

服务的具体节点,包含 IP、端口、权重(Weight)、健康状态、元数据(Metadata)等信息。每个实例启动时向 Nacos 注册自己的完整信息。

临时实例 vs 永久实例

类型注册方式健康检查剔除机制适用场景
临时实例(Ephemeral Instance)客户端主动注册 + 心跳维持客户端每 5s 上报心跳15s 无心跳→不健康,30s→自动剔除微服务(默认)
永久实例(Permanent Instance)手动注册 / API 注册服务端主动探测(TCP / HTTP / MySQL)不自动剔除,需手动下线数据库、Redis、传统 Dubbo 服务

保护阈值(Protection Threshold)

保护阈值是一个 0~1 之间的浮点数,表示健康实例占比的最低容忍线。当健康实例占比低于该阈值时,Nacos 触发保护模式:不再剔除任何实例,宁可让消费者拿到可能不健康的实例,也绝不丢弃所有实例导致服务完全不可用。这是 CAP 定理中 AP 倾向的典型体现。


核心原理

服务注册与发现完整流程

以下时序图展示了从 Provider 启动到 Consumer 调用的全过程:

临时实例健康检查状态流转

Distro 协议简介(AP 模式)

Distro 是 Nacos 自研的最终一致性协议,用于处理临时实例的服务注册。核心设计:

  1. 一致性哈希分发:请求按 serviceName 哈希到特定节点(命中节点),该节点负责写入。
  2. 本地写即返回:命中节点写入后立即返回成功,不等待跨节点同步,写入延迟 < 1ms。
  3. 异步复制:后台向其他节点异步推送数据,网络正常时延迟 < 500ms。
  4. 健康检查去中心化:每个节点独立对本地实例执行心跳检查,不做跨节点同步。

选择 Distro(AP)而非 Raft(CP)是刻意的工程权衡:服务发现场景中,宁可短暂不一致也绝不能完全不返回实例。

Raft 协议简介(CP 模式)

CP 模式下 Nacos 使用简化 Raft 协议保证强一致性,用于永久实例和配置管理:

阶段说明
Leader 选举节点随机超时(150ms~300ms),超时后发起投票,获得 N/2+1 票成为 Leader
日志复制Leader 将变更追加到日志,复制到所有 Follower,等待过半确认后才提交
快照定期压缩 Raft 日志,避免日志无限增长

配置管理对一致性要求高于可用性——用户的数据库密码如果读到旧值可能引发生产故障,因此配置变更走 CP 模式。


环境准备

Nacos Server 下载与启动

Windows:

# 下载(PowerShell)
Invoke-WebRequest -Uri https://github.com/alibaba/nacos/releases/download/v2.4.0/nacos-server-2.4.0.zip -OutFile nacos-server-2.4.0.zip
Expand-Archive nacos-server-2.4.0.zip -DestinationPath .
cd nacos\bin

# 单机模式启动
.\startup.cmd -m standalone

Linux / macOS:

wget https://github.com/alibaba/nacos/releases/download/v2.4.0/nacos-server-2.4.0.zip
unzip nacos-server-2.4.0.zip
cd nacos/bin

# 单机模式启动
sh startup.sh -m standalone

控制台访问

项目值
访问地址http://127.0.0.1:8848/nacos
默认账号nacos
默认密码nacos

登录后可在「服务管理 → 服务列表」查看已注册的所有服务。


完整示例1:服务注册与发现(RestTemplate + LoadBalancer)

场景

飞翔科技需要构建用户服务(User Service)和订单服务(Order Service)。架构师白歌负责用户服务端(Provider),后端开发小崔负责订单服务端(Consumer)。订单服务需要通过 Nacos 发现用户服务,实现服务名调用,彻底告别硬编码 IP:Port。

操作前:单体应用直连

订单服务 → http://192.168.1.10:8080/user/1

问题:用户服务扩容到 3 台后,订单服务需要手动维护 IP 列表,实例变更感知不到。

操作后:通过 Nacos 服务发现

订单服务 → http://user-service/user/1
          ↑ Nacos 自动负载均衡到底层 3 台实例之一

依赖配置

两个服务均需引入如下依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

application.yml 完整配置

用户服务(Provider,白歌开发):

server:
  port: 8080
spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: a1b2c3d4-1234-5678-9abc-def012345678  # dev 命名空间的 UUID
        group: DEFAULT_GROUP
        ephemeral: true       # 临时实例
        weight: 1.0           # 权重(1~100)
        metadata:
          version: v1         # 版本元数据
          developer: baige    # 开发者标识

命名空间在实际配置中填写的是 UUID 而非名称(如"dev")。名称仅用于 Nacos 控制台展示,注册时必须用 UUID。这是初学者最高频踩坑点。

订单服务(Consumer,小崔开发):

server:
  port: 8081
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: a1b2c3d4-1234-5678-9abc-def012345678
        group: DEFAULT_GROUP
        ephemeral: true

Provider 代码

// 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
@RestController
@RequestMapping("/user")
public class UserController {

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

    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id) {
        return "User-" + id + " from port: " + port;
    }
}

Consumer 代码

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced    // 核心:开启负载均衡 + 服务名解析
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/{id}")
    public String getOrder(@PathVariable Long id) {
        // 使用服务名替代 IP:Port,Nacos + LoadBalancer 自动解析
        String url = "http://user-service/user/" + id;
        return restTemplate.getForObject(url, String.class);
    }
}

验证

  1. 启动 Nacos Server
  2. 启动用户服务(可启动多实例,使用 --server.port=8082 参数)
  3. 启动订单服务
  4. 访问 http://127.0.0.1:8848/nacos → 服务列表,确认 user-service 和 order-service 均已注册
  5. 多次访问 http://localhost:8081/order/1,返回端口交替变化,证明负载均衡生效

完整示例2:OpenFeign 声明式调用

场景

小崔发现使用 RestTemplate 拼接 URL 字符串不够优雅,代码可读性差。白歌建议使用 OpenFeign(Open Feign,声明式 HTTP 客户端),将远程调用抽象为本地接口方法调用。

@FeignClient 注解用法

// 订单服务中定义的用户服务调用接口
@FeignClient(name = "user-service", path = "/user")
public interface UserFeignClient {

    @GetMapping("/{id}")
    String getUser(@PathVariable("id") Long id);
}
  • name:目标服务名,即 spring.application.name 的值
  • path:目标服务的统一路径前缀

@EnableFeignClients 激活

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients    // 激活 Feign 客户端扫描
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

接口定义与使用

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private UserFeignClient userFeignClient;

    @GetMapping("/{id}")
    public String getOrder(@PathVariable Long id) {
        // 像调用本地方法一样调用远程服务,底层自动通过 Nacos 发现实例
        String userInfo = userFeignClient.getUser(id);
        return "Order-" + id + " | " + userInfo;
    }
}

运行效果

启动两个服务后,进入 Nacos 控制台(http://127.0.0.1:8848/nacos):

  • 「服务管理 → 服务列表」可看到 user-service 和 order-service 两个服务
  • 点击 user-service → 「订阅者列表」可看到 order-service,说明订单服务已订阅用户服务
  • 「订阅者列表」清晰展示了服务间的调用依赖关系

完整示例3:元数据路由(灰度发布)

场景

飞翔科技的用户服务需要发布 v2 版本。白歌希望内部测试流量路由到 v2 实例,正式用户流量继续走 v1 实例,实现灰度发布(Grayscale Deployment)。

Provider:v2 版本标记

白歌启动用户服务 v2 实例时,通过 metadata.version 标记版本:

spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        metadata:
          version: v2    # 灰度版本标记
server:
  port: 8088

此时 Nacos 中 user-service 下有 2 个实例:v1(元数据 version=v1)和 v2(元数据 version=v2)。

Consumer:自定义负载均衡策略

@Configuration
public class GrayLoadBalancerConfig {

    @Bean
    public ReactorLoadBalancer<ServiceInstance> grayLoadBalancer(
            Environment env,
            LoadBalancerClientFactory factory) {
        String serviceName = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new GrayLoadBalancer(
            factory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class),
            serviceName
        );
    }
}
// 灰度负载均衡器核心逻辑
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final String serviceName;

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier =
            serviceInstanceListSupplierProvider.getIfAvailable();
        return supplier.get()
            .next()
            .map(instances -> {
                // 从请求上下文中获取灰度标记
                RequestDataContext context = (RequestDataContext) request.getContext();
                String version = context.getClientRequest()
                    .getHeaders()
                    .getFirst("version");
                // 按版本过滤实例
                List<ServiceInstance> matched = instances.stream()
                    .filter(i -> version == null ||
                        version.equals(i.getMetadata().get("version")))
                    .collect(Collectors.toList());
                if (matched.isEmpty()) {
                    // 回退到默认实例
                    matched = instances;
                }
                ServiceInstance instance = matched.get(
                    ThreadLocalRandom.current().nextInt(matched.size()));
                return new DefaultResponse(instance);
            });
    }
}

操作前后对比

阶段路由策略效果
操作前全量路由所有请求随机分发到 v1 和 v2,无法区分验证
操作后灰度路由请求 Header 带 version: v2 → 路由到 v2;普通请求 → 路由到 v1

灰度路由的核心原理:Nacos 存储实例元数据 → LoadBalancer 读取元数据 → 按 Header 匹配路由。


易错场景

命名空间填成了名称而非 UUID

错误写法:

spring:
  cloud:
    nacos:
      discovery:
        namespace: dev    # 错误!Nacos 实际使用 UUID

正确写法:

spring:
  cloud:
    nacos:
      discovery:
        namespace: a1b2c3d4-1234-5678-9abc-def012345678   # 控制台 → 命名空间 → 复制 ID

现象:服务一直注册不上,日志报 404 Not Found。在 Nacos 控制台「命名空间」页面点击对应空间,右侧「命名空间ID」列的值才是配置中应填的值。

Group 不一致导致消费者发现不到服务

白歌将用户服务注册到 PAYMENT_GROUP,小崔的订单服务默认使用 DEFAULT_GROUP 去订阅 → 发现列表为空。同一 Group 内服务才能互相发现。两个服务在开发联调前,务必确认 Group 配置一致。

临时实例心跳断开后服务列表仍有残留

Nacos 对临时实例的剔除不是实时的:15s 无心跳标记不健康 → 30s 无心跳才剔除。因此手动 Kill 进程后,Nacos 控制台可能仍有该实例展示。这是正常行为,等待约 30s 即可自动消失。

Nacos 2.x gRPC 端口 9848 未放行导致连接失败

Nacos 2.x 引入了 gRPC 通信(默认端口:主端口 + 1000 = 9848)。如果服务器或防火墙只放行了 8848,客户端将无法建立 gRPC 长连接,表现为主端口正常但注册失败。解决方案:在安全组/防火墙规则中同时放行 8848 和 9848。


面试考点

Nacos AP 与 CP 模式如何选择?各自适用什么场景?

  • AP 模式(默认):适用于服务注册与发现。服务发现场景容量大、频率高,宁可短暂不一致也不能完全不返回实例列表。底层使用 Distro 协议。
  • CP 模式:适用于配置管理。配置错误可能引发生产事故(如数据库密码错误),对一致性要求高于可用性。底层使用 Raft 协议。

Nacos 1.x 与 2.x 的心跳机制有何不同?

版本协议机制特点
1.xHTTP + UDP Push客户端每 5s 发送 HTTP 心跳,变更通过 UDP 推送单向通信,UDP 推送不可靠
2.xgRPC 双向流(Bidirectional Streaming)建立长连接,客户端主动上报 + 服务端实时推送双向通信,单连接复用(配置变更 + 服务发现共用同一连接),性能与可靠性大幅提升

保护阈值触发后会发生什么?

当健康实例占比低于保护阈值时,Nacos 进入保护模式:

  • 停止剔除不健康实例
  • 即使某些实例心跳断开,依然保留在实例列表中
  • 消费者可能拿到不健康的实例并调用失败

设计意图:宁可让消费者拿到不可用的实例(部分失败),也绝不能服务列表为空(全局不可用)。

临时实例和永久实例的区别及各自适用场景

维度临时实例永久实例
心跳机制客户端主动上报(5s 间隔)服务端主动探测(TCP / HTTP / MySQL)
剔除策略自动剔除(30s 无心跳)不自动剔除,需手动注销
一致性协议Distro(AP)Raft(CP)
适用场景微服务实例(Kubernetes Pod)基础设施(数据库 / Redis / DNS)
配置项ephemeral: trueephemeral: false
上一页
Spring Cloud Alibaba概述与技术选型
下一页
Nacos配置中心