☰
面试官:用过Nacos,那就说说Nacos服务注册的原理吧!
2026/10/7 21:30:08 网站建设 项目流程

一、面试官为什么要问这道题?

「用过 Nacos,说说服务注册的原理」,几乎是大厂后端面试的必考题。它表面上只考察一个注册流程,实际上能顺藤摸瓜问出一整套分布式系统基本功:你有没有读过源码、知不知道 AP 和 CP 的区别、心跳是怎么做的、集群之间数据怎么同步、消费者怎么感知服务上下线、挂了会不会影响业务。

因此,这道题的高分回答从来不是一句「服务提供者把 IP 和端口注册上去,消费者拉下来就能调用了」,而是要把注册链路、数据模型、一致性协议、健康检查、服务发现、推送机制、集群同步这些点串成一条有逻辑的线。

建议把握三条主线:临时实例的 AP 链路(Distro 协议)、持久实例的 CP 链路(Raft / JRaft 协议)、注册之后的信息如何被消费者及时感知(拉取 + 推送 + 长连接)。

全文提纲:

  1. Nacos 服务注册到底发生了什么

  2. 为什么需要服务注册中心

  3. Nacos 的核心概念

  4. Nacos 的整体架构

  5. 服务注册的完整流程

  6. 客户端源码分析

  7. 服务端源码分析

  8. 临时实例的核心:Distro 协议与 AP 链路

  9. 持久实例的核心:Raft / JRaft 协议与 CP 链路

  10. 心跳与健康检查

  11. 服务发现:消费者怎么拿到最新实例列表

  12. 集群同步:一个节点收到注册后如何扩散到全集群

  13. 容灾、重试与高可用设计

  14. 面试现场:高频追问与高质量回答

  15. 总结


二、先说结论:Nacos 服务注册到底发生了什么?

如果用一句话概括:服务提供者启动后,把「服务名 + IP + 端口 + 健康状态 + 元数据」封装成一个实例对象,通过注册请求发给 Nacos Server;Nacos Server 先把实例写进自己内存里的注册表(ServiceManager 维护的 Service 和 Cluster、Instance 三层模型),再根据实例是临时还是持久,走不同的持久化和一致性处理,最后把变更同步给集群其他节点,并通知订阅了该服务的消费者。

几个容易混淆的概念必须先澄清:

  • 注册(Register)不等于持久化到磁盘:默认的临时实例只存在内存里,靠客户端心跳维持存活;持久实例才会被服务端主动健康检查并保留。

  • 注册成功不等于立即强一致:临时实例走 AP,允许短暂不一致;持久实例走 CP,要求多数派确认。

  • 服务注册和服务发现是一体两面:注册端写实例,发现端订阅服务名并拿到实例列表。

  • Nacos 2.x 已经全面转向 gRPC:注册、心跳、订阅不再是单纯的 HTTP 短连接,而是基于 gRPC 长连接 + 双向流。


三、先从为什么需要服务注册中心讲起

3.1 单体时代的调用方式

在单体架构里,配置里写死数据库地址、第三方系统地址就够了:

yaml

pay: base-url: http://10.0.8.21:8080 user: base-url: http://10.0.8.22:8081

一旦系统微服务化,几十上百个服务、每个服务多实例、实例还经常扩缩容和发布,写死 IP 的维护成本会爆炸。

3.2 微服务带来的三个新问题

  • 服务地址不固定:容器化之后,Pod 或者容器的 IP 每次重启都可能变。

  • 实例数量动态变化:弹性伸缩、灰度发布、故障摘除都会让实例列表不断变化。

  • 调用方需要及时感知:服务下线后,调用方如果还拿旧地址,就会出现连接失败。

3.3 注册中心要解决的四个核心问题

  1. 服务的统一登记:服务提供者启动时把自身信息上报到一个中心节点。

  2. 服务的动态发现:消费者根据服务名查到当前可用的实例列表。

  3. 健康状态管理:通过心跳或主动探测,把不健康的实例剔除。

  4. 变更实时通知:实例上下线后,尽快告诉所有关心的订阅方。


四、Nacos 的核心概念:先把名词对齐

4.1 服务(Service)

服务由namespace+group+serviceName三元组唯一确定。

4.2 实例(Instance)

核心字段:

  • ip:实例 IP;

  • port:实例端口;

  • clusterName:所属集群;

  • healthy:是否健康;

  • weight:权重;

  • enabled:是否启用;

  • ephemeral:是否临时实例;

  • metadata:扩展元数据。

4.3 命名空间(Namespace)

用于做租户级隔离,生产、测试、灰度环境可以用不同命名空间隔开。Nacos 自带的public是默认命名空间。

4.4 分组(Group)

在同一个命名空间内做更细粒度的服务分组,默认是DEFAULT_GROUP。

4.5 集群(Cluster)

同一个服务下的实例可以划分到不同集群,比如同机房优先调用、跨机房容灾。

4.6 临时实例与持久实例

对比项临时实例(ephemeral=true)持久实例(ephemeral=false)
健康检查方式客户端主动上报心跳服务端主动探测(TCP/HTTP/MySQL 等)
数据存储默认只存内存持久化,服务端重启可恢复
一致性协议Distro(AP)Raft / JRaft(CP)
典型场景Spring Cloud 微服务、Dubbo 临时节点数据库、中间件等需要主动探测的基础设施

五、Nacos 的整体架构

三块:

  • Nacos Server:注册中心服务端,负责存储实例、健康检查、推送变更。

  • Nacos Client SDK:业务应用集成的客户端,负责注册实例、发心跳、订阅服务。

  • Nacos Console:控制台。

Nacos 2.x 引入了 gRPC 长连接,把大量请求从短连接切换成长连接上的请求响应流。


六、服务注册的完整流程:从客户端到服务端

6.1 一句话时序

  1. 应用启动,初始化NamingService;

  2. 调用registerInstance;

  3. 客户端把实例信息组装成注册请求;

  4. 通过 gRPC(2.x)或 HTTP(1.x)发给服务端;

  5. 服务端InstanceOperatorClientImpl处理请求;

  6. 写入内存注册表ServiceManager中的Service -> Cluster -> Instance;

  7. 因为默认是临时实例,走 Distro 协议,触发集群同步;

  8. 客户端侧BeatReactor启动定时心跳;

  9. 服务端收到心跳后刷新实例的lastBeat时间;

  10. 消费者订阅的服务发生变更时,服务端通过推送或长连接通知消费者更新本地缓存。

6.2 入门示例:客户端怎么注册一个实例

java

Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848"); properties.put(PropertyKeyConst.NAMESPACE, "public"); NamingService naming = NacosFactory.createNamingService(properties); Instance instance = new Instance(); instance.setIp("192.168.1.10"); instance.setPort(8080); instance.setClusterName("DEFAULT"); instance.setWeight(1.0); instance.setHealthy(true); instance.setEphemeral(true); naming.registerInstance("order-service", "DEFAULT_GROUP", instance);

七、客户端源码分析:注册请求是怎么发出的

7.1 NacosNamingService 的构造

java

public class NacosNamingService implements NamingService { private NamingClientProxy clientProxy; private BeatReactor beatReactor; private HostReactor hostReactor; ... }

7.2 registerInstance 的调用链

java

@Override public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { if (instance.isEphemeral()) { BeatInfo beatInfo = buildBeatInfo(serviceName, instance); beatReactor.addBeatInfo(groupName, serviceName, beatInfo); } clientProxy.registerService(serviceName, groupName, instance); }

临时实例在注册之前,会先把心跳信息加到 BeatReactor。

7.3 代理层选择 gRPC 还是 HTTP

java

InstanceRequest request = new InstanceRequest(); request.setNamespace(namespace); request.setServiceName(serviceName); request.setGroupName(groupName); request.setType(InstanceRequest.REGISTER); request.setInstance(instance); NamingGrpcClientProxy.this.redoService(request); requestToServer(request);

7.4 心跳是怎么发出来的

java

public void addBeatInfo(String groupName, String serviceName, BeatInfo beatInfo) { ... ScheduledFuture<?> future = executorService.scheduleWithFixedDelay( new BeatTask(beatInfo), 0, beatInterval, TimeUnit.MILLISECONDS); ... }

默认心跳间隔 5 秒,超过 15 秒不健康,超过 30 秒摘除。


八、服务端源码分析:注册表是怎么组织的

8.1 请求入口

java

public void registerInstance(Service service, Instance instance) throws NacosException { // 1. 解析并校验参数 // 2. 更新内存注册表 // 3. 根据临时/持久实例走不同的一致性协议 // 4. 触发健康检查或心跳计时 // 5. 通知订阅者 }

8.2 核心注册表:ServiceManager

java

public class ServiceManager { private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>(); ... }

外层 key 是namespace,内层 key 由group + "@@" + serviceName拼接而成。

8.3 Service 内部:Cluster 与 Instance 的两级存储

java

public class Service { private String namespaceId; private String groupName; private String name; private Map<String, Cluster> clusterMap = new ConcurrentHashMap<>(); ... } public class Cluster { private String serviceName; private String name; private Set<Instance> persistentInstances = new ConcurrentSkipListSet<>(); private Set<Instance> ephemeralInstances = new ConcurrentSkipListSet<>(); ... }

两个设计点:一是 Service 下按clusterName分桶;二是临时实例和持久实例从存储层就被分开放置。

8.4 注册表的并发安全与读写路径

Nacos 借助ConcurrentHashMap和对象级细粒度锁来保证线程安全。写入时先根据 namespace 找到内层 Map,再根据group + "@@" + serviceName拿到 Service;如果服务不存在,会通过双重检查的方式创建新的 Service 并放入注册表。读取时从内存注册表组装结果,单次查询耗时能做到毫秒级。


九、临时实例的核心:Distro 协议与 AP 链路

9.1 为什么临时实例默认不落盘

  1. 临时实例生命周期短,扩缩容、滚动发布、容器重启都会让实例频繁上下线。

  2. 健康信息本身就是易失信息,历史心跳没有持久化价值。

  3. 业务对临时实例的诉求是可用性优先,CAP 中更偏向 AP。

9.2 Distro 协议想解决什么问题

Distro 是 Nacos 自研的轻量级一致性协议,核心思想是去中心化、按服务名做路由、异步复制。它不要求强一致性,只保证最终一致。

9.3 注册写入与异步同步流程

  1. 请求进入InstanceOperatorClientImpl,识别出是临时实例;

  2. 先把实例写入本地ServiceManager的Service -> Cluster -> ephemeralInstances;

  3. 本地写入成功后立即返回客户端,不等待其他节点确认;

  4. 同时把这次变更封装成 Distro 数据同步任务,异步发送给集群中的其他节点;

  5. 其他节点收到后更新自己的注册表,并向各自长连接上的消费者推送变更。

9.4 Distro 的一致性为什么是最终一致

异步复制天然存在失败和延迟的可能。Nacos 通过定时任务和失败重试来收敛这种不一致:如果同步任务失败,会进入待重发队列继续尝试;同时各节点还会周期性做数据校验,把差异补齐。


十、持久实例的核心:Raft / JRaft 协议与 CP 链路

10.1 持久实例的注册流程

如果实例被标记为ephemeral=false,就是持久实例。持久实例注册时,Nacos 会把它交给 Raft 协议处理。请求不能只在单节点成功,而要写入 Raft 日志,经过集群多数派确认后,才能认为注册成功并返回客户端。

10.2 为什么 Nacos 选择 Raft

Raft 的三个核心机制:

  • Leader 选举:集群中任何时刻尽量只有一个 Leader 负责处理写请求。

  • 日志复制:Leader 把写操作组织成日志条目,复制给 Follower。

  • 安全性:只有包含最新已提交日志的节点才能成为 Leader。

Nacos 的底层默认使用 JRaft。

10.3 写入日志、多数派确认与状态机应用

  1. 客户端把写请求发给 Leader;

  2. Leader 把「注册实例」这个操作封装成日志条目,追加到本地日志;

  3. Leader 将日志复制给所有 Follower;

  4. 超过半数节点确认收到并持久化日志后,Leader 提交该条目;

  5. 各节点把日志应用到状态机,写入各自的ServiceManager内存注册表和持久化存储;

  6. Leader 向客户端返回成功。

10.4 临时实例与持久实例链路对比

维度临时实例持久实例
一致性协议Distro,APRaft / JRaft,CP
写请求延迟低,本地写成功即返回相对高,需要多数派确认
健康检查客户端主动心跳服务端主动探活
持久化默认不落盘通过 Raft 日志和快照持久化
适用场景业务微服务、弹性伸缩频繁基础设施、需要主动探测和恢复

十一、心跳与健康检查:实例是怎么被摘除的

11.1 临时实例:客户端心跳兜底存活

客户端注册成功后,BeatReactor会以固定间隔向服务端发送心跳。服务端收到心跳后,刷新实例的lastBeat时间,并重新计算健康状态。

11.2 5 秒、15 秒、30 秒分别是什么

  • 5 秒:客户端心跳发送间隔,默认为 5000 毫秒。

  • 15 秒:服务端超过 15 秒没收到心跳,就把临时实例标记为不健康。

  • 30 秒:服务端超过 30 秒还没收到心跳,就把临时实例从注册表中摘除。

11.3 持久实例:服务端主动探活

持久实例通常不具备可靠的心跳上报能力,因此换由服务端主动健康检查。Nacos 支持多种探测方式:TCP 连接检测、HTTP 接口返回码检测、MySQL 探活等。

11.4 心跳失败之后的客户端重注册

如果客户端心跳持续失败,或者服务端返回「实例不存在」,客户端的redoService逻辑会触发重新注册。


十二、服务发现:消费者怎么拿到最新实例列表

12.1 服务订阅模型

消费者调用subscribe后,Nacos 客户端会先拉取一次全量实例列表放入本地缓存,然后以长连接或轮询的方式跟踪变化。真正发起的远程调用,读的是客户端本地缓存。

12.2 1.x 时代的 HTTP + UDP 推送

实例发生变更时,服务端通过 UDP 向订阅者发送轻量级通知,客户端收到通知后主动发起一次 HTTP 查询拉取最新列表。

12.3 2.x 时代的 gRPC 双向流推送

Nacos 2.x 用 gRPC 长连接取代了「HTTP 短连接 + UDP 推送」的组合。服务端维护与每个客户端的双向流,实例变更后可以直接通过连接推送。

12.4 客户端本地缓存与服务端变更队列

客户端侧由HostReactor维护服务名到实例列表的本地缓存。服务端则通过PushService或连接管理器管理变更的及时下发。即使推送短暂失败,客户端仍会定期到服务端查询,用拉取作为推送的兜底。


十三、集群同步:一个节点收到注册后如何扩散到全集群

13.1 Nacos 集群部署形态

生产环境中的 Nacos 通常是三节点或更多节点的集群。客户端配置的是多个 server 地址,启动后会选择其中一个建立连接。如果当前节点不可用,客户端会自动切换到其他节点。

13.2 临时实例的 Distro 同步细节

每个节点都可以接收临时实例的注册请求。Distro 同步按服务名把数据分散到不同节点负责管理,同时把变更异步复制给其他节点。

13.3 持久实例的 Raft 复制

持久实例和配置数据则统一交给 JRaft 复制。集群中存在 Leader 和 Follower 之分,写请求统一由 Leader 处理并复制到多数派节点。

13.4 集群故障与一致性取舍

临时实例在部分节点不可用时仍能继续写入,代价是数据可能短暂不一致;持久实例在多数派节点存活时才能写入,牺牲部分可用性换取强一致。


十四、容灾、重试与高可用设计

14.1 客户端失败重试:redoService 与 redoTask

注册、心跳、订阅等请求失败后,会进入redoService或定时重做任务队列,由后台任务按一定策略重试。

14.2 客户端多地址与连接切换

客户端初始化时会保存多个 Nacos server 地址。当前节点宕机或连接断开时,客户端会通过连接管理器的重连机制切换到其他节点。

14.3 防雪崩:本地缓存与推空保护

客户端优先使用本地缓存。即使注册中心完全不可用,消费者仍然可以用最后一次拉取到的实例列表继续发起调用。同时,服务端在异常情况下也应避免把空列表随意推给客户端。

14.4 注册中心宕机会不会影响业务调用

已经建立订阅并且本地有缓存的调用方,短期内仍能继续工作。受影响的主要是新实例无法注册、已下线实例无法及时感知、新消费者无法拿到初次列表。因此注册中心更像服务的「控制平面」,真正运行中的调用依赖的是本地缓存和已建立的连接。


十五、面试现场:高频追问与高质量回答

15.1 临时实例和持久实例怎么选

业务微服务通常用临时实例,因为扩缩容频繁、客户端天然有心跳能力;数据库、中间件等基础设施通常用持久实例,因为需要服务端主动探活并持久化。选择的关键是看「谁来保证健康」和「是否需要强一致持久化」。

15.2 AP 和 CP 在 Nacos 里如何体现

临时实例走 Distro,写请求本地成功即返回,是 AP;持久实例走 Raft/JRaft,写请求需要多数派确认,是 CP。Nacos 允许两类实例共存,是因为不同业务对一致性和可用性的要求不同。

15.3 服务注册后消费者怎么感知

1.x 靠 UDP 通知 + HTTP 拉取;2.x 靠 gRPC 长连接双向流推送。客户端收到变更后更新本地缓存,业务调用读的是本地缓存。

15.4 心跳为什么是 5 秒、15 秒、30 秒

5 秒是客户端发送间隔,15 秒是服务端标记不健康的阈值,30 秒是摘除阈值。这套参数在「快速发现故障」和「避免误判」之间取得平衡。

15.5 注册中心挂了会不会影响业务

已订阅且有本地缓存的调用方短期内不受影响,新注册和新订阅会受影响。因此 Nacos 的高可用设计重点是客户端本地缓存和连接重试。

15.6 Nacos 2.x 相比 1.x 有什么变化

1.x 主要走 HTTP 短连接 + UDP 推送;2.x 全面转向 gRPC 长连接 + 双向流,注册、心跳、订阅、推送复用同一条连接,性能和可靠性都有提升。


十六、总结

Nacos 服务注册的核心可以压缩成三条主线:

  1. 临时实例的 AP 链路:Distro 协议,本地写成功即返回,异步复制到其他节点,靠客户端心跳维持存活。

  2. 持久实例的 CP 链路:Raft / JRaft 协议,写请求需要多数派确认,靠服务端主动探活。

  3. 服务发现链路:客户端订阅 + 本地缓存 + 服务端推送,1.x 用 UDP + HTTP,2.x 用 gRPC 双向流。

把这套体系讲清楚,再结合源码细节和集群同步机制,就能在面试中从容应对 Nacos 服务注册相关的各种追问。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询