7.8 KiB
tags, create time
| tags | create time | ||||||
|---|---|---|---|---|---|---|---|
|
2026-05-05 00:00 |
服务发现
概述
服务实例的动态变化(扩容、缩容、故障重启)让硬编码地址成为不可能。服务要回答的核心问题是:我怎么找到你?
[!question] 引出问题 假设你的订单服务有 3 个实例,运行在
10.0.1.1:8080、10.0.1.2:8080、10.0.1.3:8080。当其中一个实例扩容下线时,调用方如何得知最新的地址列表?手动改配置滚动重启?还是……有更好的办法?
两种发现模式
graph TB
subgraph Client["客户端发现模式"]
A[Service A] -->|1.查询注册中心| R1[(Registry)]
R1 -->|2.返回实例列表| A
A -->|3.自选实例直连| B[Service B]
noteA["客户端内置负载均衡逻辑"]
B -.-> noteA
end
subgraph Server["服务端发现模式"]
C[Service C] -->|请求| P[LoadBalancer Proxy]
P -->|查询注册中心| R2[(Registry)]
R2 -->|返回实例| P
P -->|转发| D[Service D]
noteC["客户端无感知,透明代理"]
D -.-> noteC
end
对比:
| 维度 | 客户端发现 | 服务端发现 |
|---|---|---|
| 典型实现 | Eureka、Consul、Nacos | Nginx、Envoy、K8s Service |
| 耦合度 | 客户端嵌入注册逻辑 | 透明代理,客户端无感知 |
| 性能 | 直连调用,延迟最低 | 多一跳代理开销 |
| 运维复杂度 | 每门语言需独立 SDK | 统一部署代理,技术无关 |
| 灵活性 | 客户端可自定义路由策略 | 受限于代理能力 |
客户端发现的优劣势分析
优势:
- 直连调用,少了一跳网络开销
- 客户端可以根据本地缓存做智能选择(如就近节点优先)
- 框架成熟,Eureka / Consul 社区案例丰富
劣势:
- 每个服务实现中都需要集成 SDK,跨语言迁移成本高
- 客户端需要处理实例列表的缓存与刷新逻辑
- 新增服务时需要修改调用方的代码或配置
服务端发现的优劣势分析
优势:
- 完全解耦——调用方只需知道 Proxy 地址
- 统一治理入口,可以在代理层加限流、熔断等逻辑
- 适合多语言团队,无需各自维护 SDK
劣势:
- 额外一跳增加延迟(通常 <1ms,高吞吐场景累积明显)
- Proxy 本身成为单点瓶颈,必须做横向扩展
- 调测复杂度上升——出了问题要看两层日志
主流注册中心选型
Nacos(推荐国内团队首选)
阿里开源,支持 AP(临时实例)+ CP(持久实例)双模型,同时提供配置管理功能。
// Nacos 服务注册与实例拉取
import (
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// 1. 创建服务端配置(连接哪个 Nacos)
sc := constant.ServerConfig{
IpAddr: "127.0.0.1",
Port: 8848,
}
上面仅展示了 serverConfig,实际创建时还需传入
ClientConfig(命名空间、超时等)。为简洁起见此处省略。
// 2. 创建 NamingClient
client, _ := clients.NewNamingClient(
map[string]any{"serverConfigs": []constant.ServerConfig{sc}},
)
// 3. 注册服务实例(默认临时实例,走心跳保活)
_ = client.RegisterInstance(vo.RegisterInstanceParam{
Ip: "10.0.0.1",
Port: 8080,
ServiceName: "order-service",
ClusterName: "DEFAULT",
})
// 4. 拉取某服务的可用实例列表(同步快照,含健康过滤)
instances, _, _ := client.SelectInstances(vo.SelectInstanceParam{
ServiceName: "order-service",
HealthyOnly: true, // 只返回健康实例
})
for _, inst := range instances {
// inst.Ip: inst.Port — 拿到后可直接发起 HTTP / gRPC 调用
}
Consul
HashiCorp 出品,基于 Raft 共识协议(CP),天然支持健康检查和 DNS 接口。
核心特点:
- 内建 KV 存储,可作为轻量配置中心
- 支持 multi-datacenter 部署
- DNS 接口:
order-service.service.consul可直接解析 IP
与 Nacos 选型对比:
| 维度 | Consul | Nacos |
|---|---|---|
| 一致性模型 | CP(Raft) | AP + CP 可切换 |
| 部署复杂度 | 需独立部署 Agent(Sidecar 模式) | Go SDK 内置,无额外代理 |
| 生态集成 | Terraform / Vault 深度绑定 | Spring Cloud / Dubbo 原生支持 |
| 适用场景 | 多云 / 混合云、基础设施层 | Java 技术栈、业务服务层 |
[!tip] Consul Sidecar 模式 Consul Agent 通常以 DaemonSet 方式跑在每台节点上,应用通过 localhost 向本地 Agent 注册。这种 Sidecar 模式天然适配服务端发现架构——应用把 Consul Agent 当作"本地注册中心"即可。
etcd + K8s Service
纯容器环境下最自然的选择。etcd 存数据,K8s 提供完整的发现和负载均衡体系。
# K8s Service — 服务端发现模式的代表
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order
ports:
- port: 80
targetPort: 8080
type: ClusterIP
调用方只需 http://order-service:80,K8s 通过 iptables/IPVS 自动实现负载均衡。
Eureka
Netflix 开源,AP 模型。注意:Eureka 2.x 已开源关闭,不建议新项目使用。已有系统在迁移到 Nacos 或 Consul 之前可以继续用。
关键机制
服务注册流程
sequenceDiagram
participant S as 服务实例
participant R as 注册中心
participant H as 健康检查器
S->>R: 1. Register(instance)
R-->>S: 2. ACK
Note over S,R: 实例写入内存 / etcd
loop 定时心跳
S->>R: 3. Heartbeat (每 5s)
R-->>S: 4. ACK
end
R->>H: 5. 超过心跳间隔未收到 → 标记不健康
H->>R: 6. 摘除实例
临时实例 vs 持久实例
| 类型 | 宕机检测方式 | 数据存储 | 适用场景 |
|---|---|---|---|
| 临时实例 (ephemeral) | 心跳超时自动摘除 | 内存 | Web 服务、应用实例 |
| 持久实例 (persistent) | 主动注销才摘除 | etcd | 数据库连接、外部依赖 |
[!warning] 常见陷阱 把数据库当成临时实例注册 —— 数据库挂了不需要被 "检测到",它自己控制启停。应该用持久实例。
优雅上下线
服务下线时不能直接断网,否则正在处理的请求会失败。
上线流程:
- 进程启动 → 加载配置、初始化连接池
/ready探针返回 200 → 注册中心注册 + K8s 加入负载均衡池- 开始接收流量
下线流程:
- 注册中心注销实例 → 停止接收新请求
- 等待正在处理的请求完成(最大等待 N 秒)
- 关闭连接池、保存状态
- 进程退出
[!tip] K8s Pod 优雅终止 以 sidecar 方式集成 Consul Agent 时,Pod 配置如下:
spec: terminationGracePeriodSeconds: 30 # 最多等 30 秒再 SIGKILL initContainers: - name: consul-init # 先启动 Sidecar image: consul:latest command: ["consul", "agent", "-bind", "-config-file=/etc/consul.d/server.json"] containers: - name: app lifecycle: preStop: exec: command: ["sh", "-c", "sleep 15"] # 先停流量(让 LB / Consul 摘流) postStart: exec: command: ["sh", "-c", "sleep 5"] # 等 Sidecar 就绪后再开流量关键点:preStop sleep 留给 LB / Consul 摘流时间,postStart sleep 确保 Sidecar 已准备好再做健康检查。
关联笔记
- 02-服务治理/01-API网关 — API Gateway 是服务发现的用户之一
- 02-服务治理/06-容错模式 — 健康检查是容错体系的基础
- 05-部署运维/02-Kubernetes — K8s Service 的服务发现原理