服务注册与发现:从 Nacos 到 Go 落地

微服务 A 要调用微服务 B,怎么知道 B 在哪个 IP 哪个端口?——服务发现是微服务的第一块地基。我们用 Nacos 落地了注册发现,这篇文章是完整实践。

一、为什么需要服务发现

没有服务发现的世界:

B A 5 I P I P

有服务发现的架构:

B A B B " I B P : P o " r t + A

二、核心概念

概念 作用
注册 服务启动时上报自己(IP、端口、权重、元数据)
心跳续约 每 5s 发心跳,证明"我还活着"
摘除 心跳超时(默认 15s)→ 从可用列表移除
订阅 调用方监听服务变更(新增/摘除实例)
负载均衡 拿到实例列表后轮询/随机/权重选择

三、Nacos 注册(Go 客户端)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import "github.com/nacos-group/nacos-sdk-go/v2/clients"

// 1. 启动时注册
client, _ := clients.NewNamingClient(vo.NacosClientParam{...})
client.RegisterInstance(vo.RegisterInstanceParam{
    Ip:          localIP,       // 本机 IP
    Port:        8080,          // 服务端口
    ServiceName: "delivery-service",
    Weight:      1,             // 权重(灰度用)
    Metadata:    map[string]string{"version": "v1", "env": "prod"},
    Enable:      true,
    Healthy:     true,
})

// 2. 优雅退出时注销
client.DeregisterInstance(...)

// 3. 调用方:获取实例 + 负载均衡
instances, _ := client.SelectInstances(vo.SelectInstancesParam{
    ServiceName: "delivery-service",
    HealthyOnly: true,
})
target := lb.Pick(instances)   // 轮询/随机/一致性哈希

四、负载均衡策略选择

策略 适用 特点
轮询 同权重实例 最简单
加权轮询 机器规格不同 大规格多权重
一致性哈希 需要粘性会话 同 key 永远同一实例
最小连接 长连接服务 实时性最好

简历服务用加权轮询 + 一致性哈希兜底:普通请求轮询(均匀),带 user_id 的请求哈希(同一用户命中同一实例,本地缓存命中率高)。

五、健康检查:注册 ≠ 可用

注册成功 ≠ 能接流量。常见问题:

= / 5 0 0

解法:就绪钩子。服务启动流程改为:

N a c o s / R e / g i s t e r I n s t a n c e ( E n a b l e = f a l s e ) E n a b l e = t r u e
1
2
3
4
// 就绪前注册为 disabled
client.RegisterInstance(paramWithEnable(false))
// 就绪后
client.UpdateInstance(paramWithEnable(true))

六、踩坑记录

坑 1:本地 IP 注册错

多网卡机器注册了错误的 IP(docker0/虚拟网卡),调用方连不上。

解法:显式指定注册 IP(环境变量 SERVICE_IP),别自动探测。

坑 2:实例抖动(注册-注销循环)

优雅退出超时短(比如 3s),流量还在,实例被摘 → 大量 503。

解法优雅下线流程——先 Enable=false(不再接新流量)→ 等 10s(存量请求处理完)→ 再注销。先摘流量,再杀进程

坑 3:集群脑裂

Nacos 集群网络分区,两边各自为政,实例列表不一致。

解法:Nacos 集群节点数 ≥ 3,选主机制(Raft),别用单节点。

坑 4:缓存了实例列表不更新

调用方拿到实例列表后本地缓存,服务缩容后还在调已下线实例。

解法:订阅变更事件(Subscribe),或缓存短 TTL(5s),别缓存 1 分钟。

七、故障演练:注册中心挂了会怎样

这是最重要的认知:注册中心挂了,服务不能挂!

B / A A A + B B

设计原则:注册中心是"索引"不是"依赖"——调用链路不能每请求都打注册中心,本地缓存 + 变更通知才是正确姿势。

总结

服务发现的完整认知:

  1. 注册 + 心跳 + 摘除:实例生命周期的三个环节
  2. 就绪才注册:避免"注册了但没准备好"的假健康
  3. 优雅下线:先摘流量再杀进程,顺序不能反
  4. 本地缓存兜底:注册中心挂了服务不能挂

服务发现解决的是"动态"问题:实例在变、IP 在变、故障在发生。设计目标是"任何变动都不影响调用",而不是"永远不变"。