Skip to content

服务发现

配置中心管的是"应用读什么配置",服务发现管的是另一件事——"一个服务名下面当前有哪些能连的实例"。Nacos 同时做这两件事,但它们是两条独立的链路,部署完要分开验证。这篇讲服务发现这条线。

一、服务与实例

服务发现里有个最基础的概念要先分清:服务是逻辑名,实例是具体的能连地址order-service 是服务名,它下面挂着多个实例,这些实例会随着发布、扩容、机器故障变化:

服务注册和查询时会反复出现这些字段,得能看懂:

字段说明示例
serviceName服务名order-service
groupName服务分组DEFAULT_GROUP
ip实例 IP192.168.10.13
port实例端口18080
clusterName集群名DEFAULT
healthy是否健康true
weight权重1.0
metadata附加信息version=v1zone=az1

这里有个容易忽略的点:Nacos 返回的不只是 IP:PORT,还带着健康状态、权重、元数据。客户端可以基于这些信息做负载均衡(按权重分配请求)、灰度路由(按版本标签过滤)、就近访问(按 zone 筛选)——不只是"给我个地址我连过去"这么简单。

二、临时实例与持久实例

实例分两种,行为差别很大。

临时实例是普通微服务最常用的。客户端 SDK 注册后持续发心跳,Nacos 超过一定时间收不到心跳就标记不健康,再超时就摘除。说白了就是——服务进程一停,实例就从注册中心消失了,调用方后续不会拿到它。

持久实例反过来,是人工通过 API 或控制台注册的一条固定记录,不依赖客户端心跳自动删除。适合少量固定后端,或者需要人工维护的服务条目。

bash
# 注册临时实例(默认)
curl -sS -X POST 'http://127.0.0.1:8848/nacos/v3/client/ns/instance' \
  -d 'serviceName=ops-demo-service' \
  -d 'groupName=DEFAULT_GROUP' \
  -d 'ip=192.168.10.13' \
  -d 'port=18080' \
  -d 'username=nacos' \
  -d 'password=nacos'

# 注册持久实例
curl -sS -X POST 'http://127.0.0.1:8848/nacos/v3/client/ns/instance' \
  -d 'serviceName=ops-demo-persistent' \
  -d 'groupName=DEFAULT_GROUP' \
  -d 'ip=192.168.10.13' \
  -d 'port=18080' \
  -d 'ephemeral=false' \
  -d 'username=nacos' \
  -d 'password=nacos'

查询实例列表:

bash
curl -sS \
  'http://127.0.0.1:8848/nacos/v3/client/ns/instance/list?serviceName=ops-demo-service&groupName=DEFAULT_GROUP&username=nacos&password=nacos'

返回字段关注 healthy(是否健康)、enabled(是否启用)、ephemeral(是否临时实例)、weight(权重)。

有个容易误判的坑:命令行手工注册临时实例适合验证 API,但它没有真实客户端维持心跳——过一会儿实例会被标记不健康然后消失。这是正常的,不代表 Nacos 有问题。真实业务应用由 SDK 注册,SDK 会处理心跳续约和断线重连。

三、心跳机制

临时实例靠心跳活着。应用定期告诉 Nacos"我还活着",Nacos 超过一段时间没收到心跳就把实例标记不健康:

一个特别常见的故障场景:应用日志里反复报 Nacos 连接失败,几分钟后调用方开始找不到这个服务。这时候去控制台看,服务名还在,但实例健康状态变了。"服务名存在"不等于"调用正常"——健康实例数和客户端日志才是关键。

心跳的几个关键时间参数:

参数默认值含义
心跳间隔5s客户端每隔多久发一次心跳
不健康阈值15s超过这个时间没收到心跳,标记为不健康
摘除阈值30s不健康状态持续超过这个时间,从列表里摘除

网络偶尔波动、JVM GC 短暂暂停(几百毫秒到一两秒),通常不会导致心跳超时。真正会触发摘除的是:持续 GC 长暂停(秒级甚至十几秒)、网络持续不通、应用线程池满载导致心跳线程根本没机会执行。

四、权重与元数据

权重用来做简单的流量分配:权重越高,被客户端选中的概率越大。几个常见用途:灰度少量流量时新版本实例权重调低(比如 0.1),老版本保持 1.0;下线前摘流量时权重调低到 0 或直接禁用实例;机器性能不同时高配机器给更高权重。

场景操作
灰度少量流量新版本实例权重调低(比如 0.1),老版本保持 1.0
下线前摘流量权重调低到 0 或直接禁用实例
机器性能不同高配机器给更高权重

元数据是实例上的键值对,常用于版本、机房、区域这些标识:

text
version=v2
zone=shanghai-a
role=gray

元数据只能放稳定、可解释的字段。临时排查随手加的标签如果不清理,后面会变成脏条件——比如一个实例的元数据里同时有 version=v1role=gray,而灰度规则又是按 role 过滤,行为就和预期对不上了。这种坑排查起来很费劲。

五、客户端负载均衡

应用从 Nacos 拿到实例列表后,怎么选调用哪个实例,是客户端负载均衡策略决定的,不是 Nacos 管的:

策略行为适用场景
轮询按顺序逐个分配实例配置相同、请求耗时相近
随机随机选一个简单场景
加权按权重比例分配灰度、机器性能不同
最少连接选当前连接数最少的实例请求耗时差异大
一致性哈希同一请求参数总是路由到同一实例有状态服务、缓存亲和性

Nacos 提供实例列表和权重,具体的负载均衡算法由客户端框架(Spring Cloud LoadBalancer、Dubbo、gRPC 等)实现。很多人一开始会以为 Nacos 负责决定请求打到哪台——其实不是,Nacos 只给名单,选谁由客户端框架说了算。

六、排查层次

"服务发现失败"看起来是个笼统的问题,但按链路分层排查会清晰很多:

层次检查内容常见问题
应用侧客户端是否成功注册,日志里有没有连接/鉴权异常serviceName 为空、连错 Nacos 地址
Nacos 服务侧服务名、分组、命名空间是否一致namespace 不一致导致"查不到"
实例侧健康状态、IP/端口、权重心跳断了、端口没有监听
网络侧应用到 Nacos 的 8848/9848 是否通防火墙只放了 8080
消费侧调用方是否拿到最新实例列表,本地缓存是否过期客户端缓存了旧列表

出现"服务不存在"时,把调用方使用的 namespace/group/serviceName 写出来,再和控制台逐一比对。大量"服务不存在"的问题,最后发现是:开发调的是 dev namespace,测试同学在 test namespace 的 Nacos 控制台查——看起来同名服务,实际上两个命名空间互不相通。这种"同名但不同空间"的坑,排查时一定要把 namespace 拎出来对。

还有一个容易忽略的点:调用方在启动时就向 Nacos 订阅了服务列表,之后 Nacos 通过推送和定时拉取保持更新。但如果调用方因为网络问题断开了和 Nacos 的长连接,又没及时重连,它可能拿着几分钟甚至更久之前的旧列表在调用——其中包括已经下线的实例。这种场景下,调用方日志里能看到"连接 Nacos 失败"或"重连中"的记录,顺着这个线索查。