Appearance
服务发现
配置中心管的是"应用读什么配置",服务发现管的是另一件事——"一个服务名下面当前有哪些能连的实例"。Nacos 同时做这两件事,但它们是两条独立的链路,部署完要分开验证。这篇讲服务发现这条线。
一、服务与实例
服务发现里有个最基础的概念要先分清:服务是逻辑名,实例是具体的能连地址。order-service 是服务名,它下面挂着多个实例,这些实例会随着发布、扩容、机器故障变化:
服务注册和查询时会反复出现这些字段,得能看懂:
| 字段 | 说明 | 示例 |
|---|---|---|
serviceName | 服务名 | order-service |
groupName | 服务分组 | DEFAULT_GROUP |
ip | 实例 IP | 192.168.10.13 |
port | 实例端口 | 18080 |
clusterName | 集群名 | DEFAULT |
healthy | 是否健康 | true |
weight | 权重 | 1.0 |
metadata | 附加信息 | version=v1、zone=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=v1 和 role=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 失败"或"重连中"的记录,顺着这个线索查。