Nacos 如何实现服务注册与发现的?

#Java#面试八股

Nacos 如何实现服务注册与发现的?

结论先行:Nacos 分为服务端(命名服务 + 配置服务)与客户端。注册时客户端把 namespace/group/service/cluster/ip:port/weight/metadata 上报,临时实例写入内存并靠心跳保活,持久实例写入存储并走 Raft;发现时客户端定时拉取 + 服务端推送双通道,并在本地保留快照,注册中心不可用时降级读快照。

1. 整体结构

                    ┌──────────── Nacos Server 集群 ────────────┐
                    │  Naming 模块            Config 模块        │
                    │  ├ 临时实例:内存 + Distro 同步(AP)      │
                    │  └ 持久实例:JRaft + 存储(CP)            │
                    └───────────────▲───────────────────────────┘
        注册/心跳/查询(1.x HTTP,2.x gRPC 长连接)
                                    │
   ┌────────────────────────────────┴──────────────────────────────┐
   │  Provider:启动注册,每 5s 心跳                                │
   │  Consumer:订阅服务,本地缓存实例列表,收到变更推送后刷新        │
   └───────────────────────────────────────────────────────────────┘

2. 注册流程

步骤动作说明
1客户端构造实例信息ip、port、weight、cluster、metadata、ephemeral
2调用 registerInstance1.x 走 HTTP POST /nacos/v1/ns/instance,2.x 走 gRPC
3服务端校验并写入临时实例进内存 Service 的实例表;持久实例落盘
4集群同步临时实例由 Distro 异步广播到其他节点;持久实例走 Raft 复制
5返回注册结果客户端记录本地状态,开始心跳

3. 心跳与健康检查

t=0s    注册成功
t=5s    心跳(默认每 5s 一次)
t=15s   服务端标记 unhealthy(超过 15s 未收到心跳)
t=30s   从实例列表剔除(超过 30s 未收到心跳)
参数默认值含义
heartbeatInterval5s客户端心跳间隔
heartbeatTimeout15s超过则标记不健康
ipDeleteTimeout30s超过则剔除实例
保护阈值0健康实例比例低于阈值时,仍返回不健康实例(防雪崩)

持久实例不靠心跳,而由服务端主动探测(TCP/HTTP/MySQL 等),探测失败标记不健康。

4. 服务发现:拉 + 推

Consumer 启动
  ├─ ① 订阅:subscribe(serviceName, clusters)
  ├─ ② 立即拉取全量实例:getAllInstances
  ├─ ③ 写入本地缓存(内存 + 磁盘快照)
  └─ ④ 等待推送:服务端实例变更 → UDP(1.x)/ gRPC 长连接(2.x)通知
        └─ 收到通知后重新拉取增量/全量,更新本地缓存
机制优点缺点
定时拉取(1.x 默认 10s)实现简单、最终一致变更延迟、注册中心压力大
服务端推送变更秒级感知需要维护长连接/UDP 通道
本地快照注册中心宕机仍可调用数据可能过期

两者结合:推送负责「快」,拉取负责「兜底对账」。所以 Nacos 客户端实际是「推送触发 + 拉取校正」。

5. 客户端配置

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: 8f3c1a2b-...        # 用 ID,不要用名称
        group: TRADE
        cluster-name: SH
        ephemeral: true
        register-enabled: true
        watch-delay: 30000             # 订阅推送延迟合并

踩坑点:namespace 必须填命名空间 ID,填名称会注册到 public;group 不一致会导致「注册成功但调用方发现不了」;cluster-name 影响同机房优先策略。

6. 服务端数据模型

namespace(环境隔离)
  └─ group(业务分组,默认 DEFAULT_GROUP)
       └─ service(服务名)
            ├─ cluster(机房/可用区)
            │    └─ instance(ip:port, weight, healthy, ephemeral)
            └─ cluster ...

调用方查询时可指定 group + clusters,Nacos 只返回匹配的实例,天然支持多机房就近调用。

7. 常见追问

追问一:Nacos 2.x 为什么改用 gRPC? 1.x 的 HTTP 短连接 + UDP 推送在实例规模大时开销高。2.x 用 gRPC 长连接统一注册、心跳、订阅推送,服务端能实时感知连接断开,剔除更及时(连接断即触发下线)。

追问二:临时实例和持久实例怎么选? 绝大多数微服务用临时实例(AP,心跳保活,宕机快速剔除)。需要「实例信息长期存在、不因客户端掉线而消失」的场景(如 DNS 风格的服务、数据库地址)才用持久实例。

追问三:注册中心全挂,调用链会断吗? 不会立即断。消费者有本地快照,可以继续按旧列表调用;但新实例上线感知不到,扩缩容失效。生产上 Nacos 必须集群部署(至少 3 节点)。

追问四:Distro 协议是什么? Nacos 自研的 AP 协议:每个节点负责一部分服务的写,收到写请求后异步广播给其他节点,读可以在任意节点完成。它保证最终一致,不保证强一致,换来的是高可用与低延迟。

追问五:如何做到「同机房优先」? 实例注册时带 cluster-name,消费端配置相同的 cluster-name,Nacos 返回实例时优先同集群;同集群全部不可用时才回退到其他集群。

小结

Nacos 的注册发现可以概括为:注册写内存/存储,心跳保活,Distro/Raft 做集群同步,消费端「推送 + 拉取 + 本地快照」三件套保证感知与容灾。理解这套机制,才能解释「为什么实例下线还有几十秒流量」「为什么注册中心挂了还能调用」这些线上现象。

笔记加载中…