178|Nacos原理:注册中心、配置中心、AP/CP模式和推拉结合

微服务与服务治理约 17 分钟阅读9,757 字符
知识库文档 180 / 251

阅读时间:约 45 分钟 前置知识:熟悉微服务基本概念、了解服务注册发现的基本流程 你将收获:理解 Nacos 核心架构设计、掌握注册中心与配置中心的工作机制、深入 AP/CP 模式切换原理、理解推拉结合的实时性保障


一、为什么需要 Nacos?

在微服务架构中,服务实例的数量和位置是动态变化的。一个订单服务可能今天部署了 5 个实例,明天扩容到 20 个。服务之间如何相互发现?调用方怎么知道该把请求发给哪个 IP?配置变更了,难道要重启所有服务?

Nacos(Dynamic Naming and Configuration Service)就是为解决这些问题而生的。它的名字里藏着两个核心能力:Naming(命名服务/注册中心) 和 Configuration(配置中心)。阿里巴巴在 2018 年将其开源,目前已是 CNCF 孵化项目,在国内微服务生态中几乎成了标配。

先看一张整体架构图,建立全局认知:

文章配图
文章配图

mermaid
graph TB
    subgraph "Nacos Server 集群"
        N1[Nacos Node 1<br/>AP 模式]
        N2[Nacos Node 2<br/>AP 模式]
        N3[Nacos Node 3<br/>AP 模式]
        N1 --- N2 --- N3 --- N1
    end

    subgraph "服务提供者"
        P1[订单服务<br/>实例 1<br/>192.168.1.10:8080]
        P2[订单服务<br/>实例 2<br/>192.168.1.11:8080]
    end

    subgraph "服务消费者"
        C1[网关服务<br/>订阅订单服务]
        C2[用户服务<br/>订阅订单服务]
    end

    subgraph "配置管理"
        CFG[(配置存储<br/>MySQL / Derby)]
    end

    P1 -->|1. 注册 / 心跳| N1
    P2 -->|1. 注册 / 心跳| N2
    C1 -->|2. 订阅 / 拉取实例列表| N1
    C2 -->|2. 订阅 / 拉取实例列表| N2
    N1 -->|3. 推送变更| C1
    N2 -->|3. 推送变更| C2
    N1 --- CFG
    N2 --- CFG

图 1:Nacos 整体架构——注册中心工作流

这张图展示了 Nacos 的三个核心角色:Nacos Server 集群(服务端)、服务提供者(Provider)、服务消费者(Consumer)。服务提供者启动时向 Nacos 注册,消费者从 Nacos 订阅服务列表,当实例发生变化时 Nacos 主动推送通知。配置中心的流程类似,只是"注册"变成了"发布配置","订阅"变成了"监听配置变更"。


二、注册中心:服务如何被发现

2.1 数据模型

Nacos 的注册中心数据模型非常简洁,核心只有三个层级:

code
Namespace(命名空间)→ Group(分组)→ Service(服务)→ Instance(实例)
  • Namespace:实现多租户隔离。不同的 Namespace 之间服务完全不可见。生产环境、测试环境用不同的 Namespace 隔离。
  • Group:同一个 Namespace 内的进一步分组。比如"交易"分组和"营销"分组。
  • Service:一个微服务,比如 order-service。
  • Instance:服务的一个具体实例,包含 IP、端口、权重、健康状态等元数据。

最妙的是,Nacos 用同一个 Namespace/Group/Service 的三级结构来组织注册中心和配置中心的数据,这种设计统一性极大降低了使用者的心智负担。

2.2 注册流程

服务实例注册到 Nacos 的完整流程:

文章配图
文章配图

mermaid
sequenceDiagram
    participant P as 服务提供者
    participant N as Nacos Server
    participant C as 服务消费者
    participant DB as 持久化存储

    P->>N: POST /nacos/v1/ns/instance<br/>注册实例 (ip, port, serviceName...)
    N->>N: 写入内存注册表
    N->>DB: 异步持久化到 MySQL
    N-->>P: 注册成功

    loop 每 5 秒
        P->>N: PUT /nacos/v1/ns/instance/beat<br/>发送心跳
        N->>N: 更新 lastBeat 时间戳
        N-->>P: 心跳确认
    end

    Note over N: 15 秒未收到心跳 → 标记不健康<br/>30 秒未收到心跳 → 摘除实例

    C->>N: GET /nacos/v1/ns/instance/list<br/>获取服务实例列表
    N-->>C: 返回实例列表

    N->>C: UDP 推送 (或 gRPC 长连接推送)<br/>通知实例变更

图 2:Nacos 注册中心完整交互流程

关键点有三个:

(1)临时实例 vs 持久化实例

Nacos 把实例分为两类:

特性 临时实例(ephemeral=true,默认) 持久化实例(ephemeral=false)
健康检查 客户端主动上报心跳 服务端主动探测
心跳机制 HTTP 心跳(5s 间隔) 服务端 TCP/HTTP/Mysql 探测
摘除机制 心跳超时自动摘除 不会自动摘除,需人工操作
典型场景 微服务 Pod/容器 DNS、数据库等基础设施

临时实例适用于 K8s Pod 这类生命周期短、频繁变化的场景;持久化实例适用于数据库、MQ 这类相对稳定的基础设施服务。这个设计让 Nacos 既能管理微服务,也能做 DNS 服务发现。

(2)心跳保活

客户端每 5 秒发送一次心跳,Nacos 服务端在 15 秒内没收到心跳就把实例标记为"不健康",30 秒内没收到就彻底摘除。这个时间窗口是可配置的,但默认值在大多数场景下都够用。

(3)服务端健康检查的并发控制

Nacos 2.x 引入了健康检查的任务分片机制。当集群中有多个 Nacos 节点时,健康检查任务会在节点间均匀分配,避免所有节点都去探测同一个实例,减少不必要的网络开销。

2.3 服务发现的推拉模型

这是 Nacos 最精妙的设计之一。它没有选择纯推(Push)或纯拉(Pull),而是推拉结合:

文章配图
文章配图

mermaid
graph LR
    subgraph "拉模型 (Pull)"
        A1[客户端定期轮询] --> A2[服务端返回最新数据]
        A2 --> A3[简单可靠但实时性差]
    end

    subgraph "推模型 (Push)"
        B1[服务端主动推送] --> B2[客户端收到变更通知]
        B2 --> B3[实时性高但实现复杂]
    end

    subgraph "Nacos 推拉结合"
        C1[客户端首次订阅] --> C2[全量拉取实例列表]
        C2 --> C3[建立长连接/UDP 通道]
        C3 --> C4[服务端变更时推送 diff]
        C4 --> C5[客户端本地合并更新]
        C5 --> C6[定期全量对账保证最终一致]
    end

图 3:Nacos 推拉结合模型对比纯推/纯拉

具体的工作机制:

第一阶段:全量拉取。 客户端首次订阅服务时,向 Nacos 发起一次全量拉取,获取当前所有健康实例的列表。这是"拉"的部分。

第二阶段:变更推送。 Nacos 2.x 通过 gRPC 长连接向客户端推送变更事件。当服务实例上线、下线、权重变化时,Nacos 立即通过这个长连接将变更推送给所有订阅该服务的客户端。这是"推"的部分。

第三阶段:定期对账。 为了防止推送丢失(网络抖动、客户端重启等),客户端会定期(默认 10 秒)发起一次轻量级查询,用 MD5 校验和比对本地缓存与服务端数据是否一致。如果不一致,就重新全量拉取。这是"拉"的兜底部分。

这种设计在实时性和可靠性之间取得了很好的平衡。用行话来说就是:"推"保证实时性,"拉"保证最终一致性。

2.4 Nacos 1.x vs 2.x 的通信模型演进

Nacos 1.x 的通信模型相对简单但存在不少问题:

方面 Nacos 1.x Nacos 2.x
客户端-服务端通信 HTTP + UDP gRPC(双向流)+ HTTP
服务发现推送 UDP 推送(不可靠) gRPC 双向流推送(可靠)
连接管理 无连接状态,每次 HTTP 请求独立 长连接管理,连接复用
心跳机制 HTTP 短连接心跳 gRPC 长连接心跳
性能瓶颈 1.x 服务端压测 HTTP 连接数受限 2.x 单节点可支撑 10 万+ 客户端
  1. x 时代最大的痛点是用 UDP 推送变更通知。UDP 不保证送达,丢包了就丢了,客户端只能靠定期轮询来兜底——这导致通知延迟可能高达数秒。2.x 引入 gRPC 双向流后,推送变成了可靠的流式传输,实时性大幅提升。

三、配置中心:配置如何动态生效

3.1 配置中心的本质问题

你有没有经历过这样的场景:线上突然需要调整某个开关,或者限流阈值需要紧急下调,但改配置就得重启服务?重启意味着服务中断,哪怕只有几十秒,在核心链路上也是不可接受的。

配置中心解决的就是这个问题:让配置变更在不重启的情况下实时生效。

3.2 配置模型

Nacos 配置中心的数据模型:

code
Namespace → Group → Data ID(配置项)
  • Data ID:配置的唯一标识,通常格式为 ${applicationName}-${profile}.${extension},比如 order-service-dev.yaml。
  • Group:配置分组,默认 DEFAULT_GROUP。
  • Namespace:租户隔离,和注册中心共用。

3.3 配置发布与监听流程

文章配图
文章配图

mermaid
sequenceDiagram
    participant Admin as 运维/开发
    participant N as Nacos Server
    participant DB as MySQL
    participant App1 as 应用实例 A
    participant App2 as 应用实例 B

    Admin->>N: 发布配置<br/>(dataId, group, content)
    N->>DB: 持久化配置内容
    N->>DB: 记录配置历史版本
    N-->>Admin: 发布成功

    App1->>N: 建立 gRPC 长连接<br/>注册配置监听 (dataId, group, MD5)
    App2->>N: 建立 gRPC 长连接<br/>注册配置监听 (dataId, group, MD5)

    N->>N: 检测 MD5 变化
    N->>App1: 推送配置变更通知<br/>(dataId, group, new MD5)
    N->>App2: 推送配置变更通知<br/>(dataId, group, new MD5)

    App1->>N: 拉取最新配置内容
    App2->>N: 拉取最新配置内容

    App1->>App1: 本地应用新配置<br/>(刷新 Bean / 重新加载)
    App2->>App2: 本地应用新配置<br/>(刷新 Bean / 重新加载)

图 4:Nacos 配置中心发布与监听全流程

3.4 配置变更的回滚与审计

配置中心不只是"存配置"和"推送配置"。企业级配置中心的核心价值在于变更管控:

  • 版本管理:每次配置变更都会生成一个新版本,可以在历史版本列表里查看任意历史版本的内容。
  • 一键回滚:如果新配置上线后发现问题,可以一键回滚到任意历史版本。回滚本质上就是一次"发布历史版本内容"的操作。
  • 变更审计:谁在什么时间改了什么配置,从哪个值改成了哪个值,都会被记录下来。
  • 灰度发布:可以指定配置只对部分 IP 生效,先在少量实例上验证,再全量发布。

3.5 配置的动态刷新机制

Spring Cloud 应用中,@RefreshScope 注解标记的 Bean 会在配置变更后被重新初始化。底层原理是:

  1. Nacos 客户端通过 gRPC 长连接收到配置变更通知。
  2. 客户端拉取新的配置内容,更新本地缓存。
  3. 发布一个 RefreshEvent 事件。
  4. Spring Cloud 的 RefreshScope 监听该事件,销毁被 @RefreshScope 标记的 Bean 的缓存。
  5. 下次访问该 Bean 时,Spring 会用新的配置值重新创建它。

这个机制的关键在于:不是热替换对象,而是销毁旧对象,延迟创建新对象。这意味着 Bean 不需要是线程安全的,因为不存在并发修改的问题。


四、AP 模式与 CP 模式:Nacos 的一致性取舍

4.1 CAP 定理回顾

在分布式系统中,CAP 定理告诉我们在一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)之间最多只能同时满足两个。由于网络分区在分布式系统中不可避免,P 必须被满足,所以实际选择是:CP 还是 AP?

  • CP(一致性 + 分区容错):发生网络分区时,牺牲可用性来保证数据一致性。宁可暂时不可用,也不能返回错误数据。
  • AP(可用性 + 分区容错):发生网络分区时,牺牲一致性来保证可用性。宁可返回可能过时的数据,也不能让服务不可用。

Nacos 的独特之处在于:它同时支持 AP 和 CP 两种模式,并且可以在注册中心和配置中心之间独立切换。

4.2 注册中心:默认 AP,可切换 CP

注册中心的核心诉求是可用性优先。原因很简单:服务发现是微服务调用的入口,如果注册中心因为网络分区而拒绝服务,整个微服务体系就瘫痪了。即使返回的实例列表略有过时(比如包含了一个刚挂掉的实例),调用方可以通过重试和熔断来兜底,远比直接不可用要好。

Nacos 注册中心的 AP 实现基于 Distro 协议(阿里巴巴自研的分布式协议):

code
┌──────────────────────────────────────────────────┐
│  Nacos 注册中心 — Distro 协议(AP 模式)            │
├──────────────────────────────────────────────────┤
│  核心思想:每个节点负责一部分数据,独立处理写入      │
│                                                  │
│  写入流程:                                       │
│  1. 请求到达任意节点                               │
│  2. 该节点判断自己是否为数据"负责人"                │
│  3. 如果是负责人:直接写入,异步同步给其他节点       │
│  4. 如果不是负责人:转发给负责节点处理              │
│                                                  │
│  一致性保证:                                     │
│  - 写入:本地先落盘,异步同步(最终一致)           │
│  - 读取:从本地内存直接返回(不保证强一致)          │
│  - 对账:定期 checksum 比对,发现不一致就全量同步    │
└──────────────────────────────────────────────────┘

Distro 协议的设计哲学是:数据分片 + 独立决策 + 最终一致。每个 Nacos 节点负责一部分服务数据,对这部分数据有完全的写入权限,不需要和其他节点协商。这样即使集群中只剩一个节点,注册和发现功能依然可用。

文章配图
文章配图

图 6:Nacos Distro 协议——AP 模式下的数据分片与独立决策机制

当注册中心切换到 CP 模式时,底层使用 JRaft(Raft 协议的 Java 实现)。这时所有写入操作需要通过 Raft 日志复制得到多数节点确认后才能返回。可用性会降低,但数据一致性得到保证。

4.3 配置中心:默认 CP,不可切换

配置中心的核心诉求是一致性优先。配置一旦发布,必须保证所有实例最终看到的是同一份配置。如果配置中心在分区时返回了不一致的配置,不同实例可能行为不一致,导致线上故障。

配置中心使用 JRaft 实现 CP 模式:

文章配图
文章配图

mermaid
graph TB
    subgraph "Nacos 配置中心 — JRaft 协议(CP 模式)"
        direction TB
        L[Leader] -->|日志复制| F1[Follower 1]
        L -->|日志复制| F2[Follower 2]
        F1 -->|确认| L
        F2 -->|确认| L

        subgraph "写入流程"
            W1[客户端发起配置变更] --> W2[请求路由到 Leader]
            W2 --> W3[Leader 写入本地日志]
            W3 --> W4[Leader 将日志复制到 Follower]
            W4 --> W5[等待多数节点确认]
            W5 --> W6[Leader 提交日志并返回成功]
        end
    end

    subgraph "持久化层"
        DB[(MySQL<br/>配置持久化)]
    end

    L --- DB
    F1 --- DB
    F2 --- DB

图 5:Nacos 配置中心的 Raft 一致性协议

配置的写入必须经过 Raft 共识:Leader 将写操作封装为日志条目,复制到 Follower 节点,等待超过半数的节点确认后,才认为写入成功。如果 Leader 宕机,剩下的 Follower 会发起新的选举,保证集群继续对外提供服务。

4.4 为什么这样设计?

场景 一致性要求 可用性要求 选择
服务注册与发现 强一致性不是必须的,短暂不一致可接受 必须高可用,否则调用链断裂 AP
配置管理 必须一致,否则不同实例行为不一致 短暂不可用可接受 CP

这种"同一套系统,不同场景不同一致性策略"的设计非常务实,避免了"一刀切"带来的问题。这也是为什么 Nacos 能在注册中心和配置中心两个领域同时做到优秀的原因。


五、Nacos 集群架构与高可用

5.1 集群部署模式

Nacos 推荐的生产部署架构:

code
                      ┌──────────────┐
                      │   Nginx/ALB  │
                      │  (VIP: 80)   │
                      └──────┬───────┘
               ┌─────────────┼─────────────┐
               │             │             │
        ┌──────▼──────┐ ┌───▼──────┐ ┌────▼──────┐
        │ Nacos Node 1│ │Nacos N.2 │ │Nacos N.3  │
        │ 192.168.1.1 │ │192.168.1.│ │192.168.1.3│
        └──────┬──────┘ └────┬─────┘ └─────┬─────┘
               │             │             │
               └─────────────┼─────────────┘
                             │
                      ┌──────▼──────┐
                      │    MySQL    │
                      │  (主从/集群) │
                      └─────────────┘

关键点:

  • Nacos 节点间通过 gRPC 通信(2.x),负责数据同步和健康检查任务分配。
  • MySQL 集群作为持久化存储,所有 Nacos 节点共享同一个数据库。这不是"分布式存储"方案,而是"共享存储"方案——Nacos 节点本身不存储数据,数据一致性由 MySQL 保证。
  • 使用 Nginx 或云负载均衡器做流量分发,客户端只需知道 VIP 地址。

文章配图
文章配图

图 7:Nacos 集群高可用部署——节点间数据同步与故障转移

5.2 Nacos 节点挂了怎么办?

Nacos 的容错机制分两层:

第一层:客户端容错。 Nacos 客户端(Java SDK)在本地维护了一份服务实例的缓存快照。即使 Nacos 服务端全部宕机,客户端依然可以使用本地缓存的服务列表继续调用——虽然不能感知到新的实例变更,但已有的调用链路不会中断。

第二层:服务端容错。 如果 Nacos 集群中部分节点宕机:

  • 注册中心(AP 模式):只要有一个节点存活,注册和发现功能就正常。Distro 协议保证每个节点独立决策。
  • 配置中心(CP 模式):需要过半节点存活才能写入。如果 3 节点集群挂了 2 个,可以读但不能写。

六、源码视角:Nacos 的关键数据结构

6.1 注册表——ServiceManager

Nacos 注册中心的核心数据结构是 ServiceManager,它维护了一个双层 ConcurrentHashMap:

java
// 简化版核心结构
public class ServiceManager {
    // 第一层 Key: namespace, 第二层 Key: group::serviceName
    private final ConcurrentHashMap<String, ConcurrentHashMap<String, Service>> serviceMap;

    // Service 内部维护实例列表
    public class Service {
        private String name;
        // 集群 -> 实例列表
        private ConcurrentHashMap<String, Cluster> clusterMap;
    }

    public class Cluster {
        private String name;
        // 持久化实例 (ephemeral=false)
        private Set<Instance> persistentInstances;
        // 临时实例 (ephemeral=true)
        private Set<Instance> ephemeralInstances;
    }
}

这个设计的精妙之处:

  • 全内存:所有注册信息都存储在 JVM 内存中,查询 O(1) 复杂度,不需要访问数据库。
  • ConcurrentHashMap:读写并发安全,读操作不加锁,写操作只锁对应分段。
  • MySQL 异步持久化:实例注册写入内存后立即返回,异步写入 MySQL 防止数据丢失。即使 MySQL 暂时不可用,注册中心也不受影响。

6.2 一致性协议切换——EphemeralConsistencyService

协议切换的关键是 ConsistencyService 接口:

java
public interface ConsistencyService {
    // 写入操作
    void put(String key, Record value);
    // 读取操作
    Record get(String key);
    // 监听变更
    void listen(String key, RecordListener listener);
}

Nacos 在启动时根据配置和数据类型选择不同的实现:

  • 临时实例数据 → DistroConsistencyServiceImpl(AP 模式)
  • 持久化实例数据 → RaftConsistencyServiceImpl(CP 模式,JRaft)
  • 配置数据 → PersistentConsistencyServiceDelegateImpl(CP 模式,JRaft)

这个策略模式的设计让 Nacos 可以在不修改业务代码的情况下,在不同一致性协议之间切换。


七、实践要点与常见问题

7.1 Nacos vs Eureka vs Consul vs ZooKeeper

很多面试会问 Nacos 和其他注册中心的对比:

特性 Nacos Eureka Consul ZooKeeper
CAP 模型 AP/CP 可切换 AP CP CP
配置中心 ✅ 内置 ❌ 需配合 Spring Cloud Config ✅ 内置 ❌ 需配合其他方案
健康检查 客户端心跳 + 服务端探测 客户端心跳 服务端探测 基于 Session 心跳
一致性协议 Distro(AP)/ Raft(CP) 无一致协议 Raft ZAB
通信协议 gRPC(2.x) HTTP HTTP/DNS/gRPC TCP
多数据中心 ✅ ❌ ✅ ❌
控制台 功能完善的 Web 控制台 基本控制台 Web UI 需第三方工具
社区活跃度 高(阿里巴巴主导) 低(已停更) 中 高(Apache 项目)

文章配图
文章配图

图 8:主流注册中心全景对比——Nacos、Eureka、Consul、ZooKeeper 架构差异

7.2 如何选择注册中心?

  • 纯 Spring Cloud 技术栈 → Nacos:与 Spring Cloud Alibaba 深度集成,注册+配置一站式解决。
  • 已有 ZooKeeper 运维经验,且不需要配置中心 → ZooKeeper:成熟稳定,但复杂度高。
  • 多数据中心、需要 Service Mesh 集成 → Consul:多数据中心支持最好。
  • Eureka:已停止维护,不建议新项目使用。

7.3 常见坑

(1)Nacos 1.x 的 UDP 推送丢失

在 1.x 版本中,如果客户端和 Nacos 服务端之间网络不稳定,UDP 推送可能丢失,导致客户端感知不到服务变更。解决方案:升级到 2.x 使用 gRPC 长连接,或者缩短客户端的轮询间隔。

(2)配置中心并发写入冲突

多个管理员同时对同一个 Data ID 进行修改时,后提交的会覆盖先提交的。Nacos 的配置管理界面没有"编辑锁"机制,需要从流程上规范——同一时间只能有一个人编辑生产配置。

(3)权重配置的误解

Nacos 实例的权重范围是 0~1(浮点数),0 表示不接收流量,1 表示正常权重。这不是"几台机器的权重比",而是"单台机器接收流量的比例"。如果需要实现 1:2:3 的流量分配,理解为三台机器分别接收 100%、200%、300% 的流量是不对的;实际上 Nacos 的权重是客户端负载均衡的参考值,最终效果取决于负载均衡算法的实现。

(4)保护阈值(Protect Threshold)

Nacos 有一个"保护阈值"的概念,默认 0(关闭)。当健康实例占总实例的比例低于这个阈值时,Nacos 会停止摘除不健康实例,防止"所有实例都被摘除导致服务完全不可用"的雪崩效应。但很多人不知道这个功能,在线上因网络抖动导致大量实例被摘除时,不会利用这个保护机制。


八、总结

Nacos 的设计哲学可以用三句话概括:

  1. 实用主义:注册中心用 AP 保证高可用,配置中心用 CP 保证数据一致,不追求理论上的一致性洁癖,而是根据实际场景做取舍。
  2. 推拉结合:首次全量拉取 + 变更增量推送 + 定期对账兜底,三层机制保证数据既实时又可靠。
  3. 统一抽象:用同一套 Namespace/Group/Data 模型管理注册和配置,降低使用复杂度。

理解和掌握 Nacos 的原理,不只是为了应对面试,更是为了在实际工作中能做出正确的架构决策——知道什么时候该用 AP 模式,什么时候该用 CP 模式,什么时候该用临时实例,什么时候该用持久化实例。这些决策直接影响着系统的稳定性和可用性。


面试官视角:请回答

以下问题从面试官视角出发,考察你对 Nacos 原理的深入理解和实际工程经验。尝试在不查阅资料的情况下回答。

Q1:Nacos 注册中心默认是 AP 模式,如果某个服务实例挂了,消费者多久能感知到?请从心跳机制、推送机制、对账机制三个层面分析延迟,并说明最坏情况下的延迟是多少。

点击查看参考答案

从三个层面分析:

  1. 心跳超时:临时实例客户端每 5 秒发送心跳,Nacos 15 秒未收到心跳标记不健康,30 秒未收到摘除。所以从实例挂掉到被 Nacos 摘除,最坏情况是 30 秒。
  2. 推送延迟:Nacos 2.x 使用 gRPC 长连接推送变更,摘除动作发生后,推送几乎是实时的(毫秒级)。Nacos 1.x 使用 UDP 推送,可能丢包,延迟取决于轮询间隔。
  3. 对账延迟:客户端每 10 秒(默认)做一次 MD5 对账,如果推送丢失,最坏情况 10 秒后对账发现不一致并重新拉取。

最坏情况:假设实例在心跳周期刚开始时挂掉,心跳 30 秒超时摘除 + 推送丢失 + 10 秒后对账发现,总共约 40 秒。这是没有调优默认参数的情况。实际生产中可以调小 nacos.naming.clean.empty-service.interval 等参数来缩短。

要注意的是,消费者调用这个已挂实例时,业务层面的熔断和重试会比 Nacos 摘除更快生效——Nacos 的摘除更多是"防止新请求被路由到已挂实例",而不是"熔断"的替代。

Q2:Nacos 配置中心使用 Raft 协议保证一致性。如果 3 节点 Nacos 集群中 Leader 挂了,配置变更会中断多久?新的 Leader 选举期间,客户端能读到配置吗?

点击查看参考答案

  1. 中断时间:Raft 的 Leader 选举超时时间默认在 150ms~300ms 之间(随机),选举过程本身通常在几十毫秒内完成。所以配置写入的中断时间通常在 200ms~500ms 之间。加上客户端重试(Nacos SDK 内置重试),对业务几乎无感知。
  2. 选举期间能否读取:可以。Raft 的读操作可以由 Follower 处理(如果开启了 ReadIndex 或 Lease Read 优化),即使没有 Leader,Follower 也能返回上次已提交的配置。配置读是"无状态"的,不需要经过 Leader。所以配置读取不会中断。
  3. 极端情况:如果 3 节点挂了 2 个,只剩 1 个节点,既无法选举出 Leader(达不到多数),也无法提交新的写入。此时配置可读不可写。这个场景下,Nacos 不脑裂,宁可牺牲可用性也要保证一致性——这正是 CP 模式的体现。

Q3:Nacos 2.x 为什么从 HTTP+UDP 改为 gRPC?从协议层面分析 gRPC 相比 HTTP+UDP 在服务发现场景下的优势。

点击查看参考答案

gRPC 相比 HTTP+UDP 的核心优势:

  1. 双向流(Bidirectional Streaming):HTTP 是请求-响应模型,服务端无法主动推送;UDP 虽然可以推送但不可靠。gRPC 基于 HTTP/2,天然支持双向流,服务端可以主动推送变更,客户端可以复用同一条连接发送心跳和接收推送。
  2. 连接复用:HTTP/1.1 是短连接模型,每次心跳都要重新建立 TCP 连接(或受限于连接池大小)。gRPC 使用长连接,一条连接承载所有通信,大幅减少连接开销,单节点可支撑的客户端数量从万级提升到十万级。
  3. 可靠性:UDP 不保证送达,丢包后需要客户端轮询兜底。gRPC 基于 TCP,推送是可靠的,不需要额外的对账机制来弥补推送丢失。
  4. 流控和多路复用:HTTP/2 的多路复用允许多个 Stream 在同一条 TCP 连接上并发传输,互不阻塞,解决了 HTTP/1.1 的队头阻塞问题。
  5. 序列化效率:gRPC 默认使用 Protobuf 序列化,比 JSON 体积更小、解析更快,在高频心跳场景下带宽优势明显。

但也要注意 gRPC 的代价:长连接需要服务端维护连接状态,连接数多时内存开销大;某些负载均衡器对 HTTP/2 长连接的支持不如 HTTP/1.1 成熟。

Q4:设计一个支持 10 万+ 实例的注册中心,需要考虑哪些核心问题?请从数据分片、一致性、推送效率、容灾几个维度给出设计方案。

点击查看参考答案

这是一个系统设计题,考察架构思维:

1. 数据分片

  • 按服务名做一致性哈希分片,每个节点负责一部分服务数据。
  • 类似 Nacos 的 Distro 协议思路:每个节点对负责的数据有写入权,对其他分片的数据只有只读副本。
  • 分片策略要支持动态 rebalance(节点上下线时数据迁移)。

2. 一致性

  • 注册中心用 AP 模型(最终一致),不要求强一致。
  • 写操作:本地写入 + 异步同步副本(类似 Gossip 或 Distro)。
  • 对账机制:定期 checksum 比对,发现不一致做全量或增量同步。
  • 配置中心单独部署,用 Raft 保证 CP。

3. 推送效率

  • 使用长连接(gRPC/WebSocket),避免轮询。
  • 推送时只发送变更 diff,不全量推送。
  • 按服务名做推送隔离:某个服务的变更只推送给订阅该服务的客户端,不广播给所有客户端。
  • 推送合并:短时间内同一服务的多次变更合并为一次推送。

4. 容灾

  • 客户端本地缓存兜底,服务端全挂也能用。
  • 多机房部署,每个机房有独立集群,机房间异步同步。
  • 灰度发布配置变更,先在少量实例上验证。
  • 配置变更支持一键回滚。

5. 性能

  • 全内存存储,注册表不落盘(MySQL 只做异步持久化)。
  • 读写分离:读从内存直接返回,写异步落盘。
  • 批量注册 API,减少网络往返。
  • 连接数和内存的平衡:单节点 10 万连接需要约 10-20GB 内存。

本文是「Java 技术专家与架构师面试:250 个高频考点总览」系列的第 178 篇。下一篇预告:179|ZooKeeper 原理:ZAB、临时节点、Watcher、一致性与典型应用。