服务发现 / Service Discovery
📅 创建时间:2026-07-28 🏷️ 标签:#ServiceDiscovery #Consul #Etcd #Eureka #Nacos #DNS #HealthCheck 📚 前置知识:[[00-overview]](基础设施组件全景)
📋 本章目标
- 理解服务发现要解决的根本问题——为什么不能写死 IP
- 掌握 DNS-based / Client-side / Server-side 三种模式的原理、优劣与适用场景
- 深入理解 Consul 的架构(Agent/Server、健康检查、KV Store)
- 理解服务注册与健康检查的三种方式(TTL/HTTP/gRPC)
- 理解 K8s 中的服务发现机制(ClusterIP/Headless/CoreDNS/EndpointSlice)
- 能够对比 Consul / Etcd / Eureka / Nacos / Zookeeper 并做出合理选型
第1部分:为什么需要服务发现
1.1 在微服务世界里,"地址"是一个移动靶
┌─────────────────────────────────────────────────────────────┐
│ 服务地址的动态性 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 单体时代(地址是固定的): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用户服务 │ │
│ │ 订单服务 → http://localhost:3001 (订单也在同一进程)│ │
│ │ 商品服务 → http://localhost:3002 │ │
│ │ 配置:application.yml,写死 IP:PORT │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 微服务时代(地址是流动的): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 时间线: │ │
│ │ T0: 订单服务启动 → IP=10.0.1.5:3001 │ │
│ │ T1: 扩容 → IP=10.0.1.6:3001, 10.0.1.7:3001 │ │
│ │ T2: 10.0.1.5 宕机 → 只剩 6 和 7 │ │
│ │ T3: 缩容 → 只剩 10.0.1.7:3001 │ │
│ │ T4: 重新部署 → IP=10.0.2.10:3001 (IP 全变了) │ │
│ │ │ │
│ │ 问题:用户服务怎么知道"订单服务现在在哪"? │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 服务发现要回答的四个问题: │
│ 1. 注册:新实例上线了,怎么让系统知道? │
│ 2. 发现:调用方需要知道有哪些可用的实例 │
│ 3. 健康检查:挂掉的实例怎么被剔除? │
│ 4. 负载均衡:这么多实例,请求该发给谁? │
│ │
└─────────────────────────────────────────────────────────────┘1.2 没有服务发现时——一切靠手工
┌─────────────────────────────────────────────────────────────┐
│ 没有服务发现的世界 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 方案一:硬编码 IP(最原始的方案) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ // user-service 代码 │ │
│ │ const ORDER_SERVICE_URL = "http://10.0.1.5:3001"; │ │
│ │ // 订单服务部署到新 IP → 改代码 → 重新构建 → 部署 │ │
│ │ // → 改一行配置 = 一次完整发布 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方案二:Nginx upstream 手动维护 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ upstream order_service { │ │
│ │ server 10.0.1.5:3001; ← 人工添加 │ │
│ │ server 10.0.1.6:3001; ← 人工添加 │ │
│ │ # server 10.0.1.7:3001; ← 下线了,人工注释掉 │ │
│ │ } │ │
│ │ → 扩缩容靠人 → nginx -s reload → 不可持续 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 结论:当服务实例数超过"人工可维护"的阈值(通常 5-10), │
│ 必须引入自动化的服务发现机制。 │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:服务发现的三种模式
2.1 全景对比图
┌─────────────────────────────────────────────────────────────┐
│ 三种模式架构对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 模式一:DNS-based(DNS 服务发现) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 服务 A ──DNS查询──▶ DNS Server ──返回IP列表──▶ 服务 A│ │
│ │ (客户端) "order-svc" (CoreDNS) [10.0.1.5, │ │
│ │ 10.0.1.6] │ │
│ │ 服务 A ──HTTP请求──▶ 10.0.1.5:3001 (订单服务) │ │
│ │ │ │
│ │ ★ 最通用的方案,任何语言/框架都能用 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 模式二:Client-side Discovery │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 服务 A ──查询"订单服务"──▶ 注册中心(Consul/Nacos) │ │
│ │ (客户端) ◀──返回所有健康实例列表── │ │
│ │ │ │
│ │ 服务 A ──自行负载均衡──▶ 选中的实例 │ │
│ │ │ │
│ │ ★ 需要客户端集成 SDK,不同语言有不同实现 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 模式三:Server-side Discovery │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 服务 A ──请求──▶ 负载均衡器(Envoy/Nginx) │ │
│ │ (客户端) │ │ │
│ │ ├──查询注册中心──▶ 健康实例列表 │ │
│ │ │ │ │
│ │ └──选择实例并转发──▶ 订单服务 │ │
│ │ │ │
│ │ ★ 客户端零感知,多一跳网络延迟 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.2 三种模式的优劣分析
┌─────────────────────────────────────────────────────────────┐
│ 三种模式对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 维度 │ DNS-based │ Client-side │ Server-side │
│ ├────────────┼────────────┼────────────┼─────────────┤
│ │ 客户端复杂度│ ⭐ 极低 │ ⭐⭐⭐ 中等 │ ⭐ 极低 │
│ │ (语言绑定) │ 无 │ 需要 SDK │ 无 │
│ ├────────────┼────────────┼────────────┼─────────────┤
│ │ 故障感知 │ 慢(TTL缓存) │ 快(心跳) │ 快(代理检测) │
│ │ (延迟) │ 30s-5min │ 1-10s │ 1-10s │
│ ├────────────┼────────────┼────────────┼─────────────┤
│ │ 网络跳数 │ 2 跳 │ 2 跳 │ 3 跳 │
│ │ │(A→DNS→B) │(A→注册中心→B)│(A→LB→注册→B)│
│ ├────────────┼────────────┼────────────┼─────────────┤
│ │ 负载均衡 │ 客户端随机 │ SDK 内置 │ LB 负责 │
│ │ │ 或 DNS RR │ 多种策略 │ 多种策略 │
│ ├────────────┼────────────┼────────────┼─────────────┤
│ │ 权重/元数据 │ 有限 │ 丰富 │ 丰富 │
│ │ 路由 │ │ │ │
│ ├────────────┼────────────┼────────────┼─────────────┤
│ │ 运维复杂度 │ ⭐ 极低 │ ⭐⭐ 低 │ ⭐⭐⭐ 中等 │
│ │ │ (DNS运维) │ (注册中心) │ (+LB运维) │
│ ├────────────┼────────────┼────────────┼─────────────┤
│ │ 代表实现 │ K8s CoreDNS│ Consul/ │ K8s Service+ │
│ │ │ Consul DNS │ Nacos/Eureka│ Istio+Envoy │
│ │
│ 一句话选型: │
│ • 已有 K8s → DNS-based(零成本) │
│ • 统一语言栈(如全 Java)→ Client-side(Dubbo + Nacos) │
│ • 多语言/已有服务网格 → Server-side(Istio + Envoy) │
│ │
└─────────────────────────────────────────────────────────────┘第3部分:Consul 架构深入
3.1 整体架构
┌─────────────────────────────────────────────────────────────┐
│ Consul 架构全景 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Consul Server Cluster(3-5 节点,Raft 共识) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Server 1 │◀─┼─▶ Server 2│◀─┼─▶ Server 3│ │ │
│ │ │ (Leader) │ │(Follower)│ │(Follower)│ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ 服务注册 │ │ KV 存储 │ │ 健康检查 │ │ │
│ │ │ DNS 查询 │ │ HTTP API │ │ ACL 控制 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Consul Agent(每个节点一个,DaemonSet 模式) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Agent │ │ Agent │ │ Agent │ │ │
│ │ │ (Node 1) │ │ (Node 2) │ │ (Node 3) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ 健康检查执行│ │ DNS 转发 │ │ 本地缓存 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 业务服务(带 Consul Client/Sidecar) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 订单服务 │ │ 用户服务 │ │ 商品服务 │ │ │
│ │ │ :3001 │ │ :3002 │ │ :3003 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Server vs Agent 的职责分离: │
│ • Server: 存储状态、共识、服务目录、KV Store │
│ • Agent: 执行健康检查、DNS 接口、转发请求到 Server │
│ • 设计意图:Agent 是无状态的,挂了不影响数据 │
│ │
└─────────────────────────────────────────────────────────────┘3.2 Consul 的核心功能
┌─────────────────────────────────────────────────────────────┐
│ Consul 五大核心能力 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 服务注册(Service Registration) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 方式一:服务主动注册(HTTP API) │ │
│ │ PUT /v1/agent/service/register │ │
│ │ { │ │
│ │ "Name": "order-service", │ │
│ │ "Port": 3001, │ │
│ │ "Tags": ["v2", "primary"], │ │
│ │ "Check": { │ │
│ │ "HTTP": "http://localhost:3001/health", │ │
│ │ "Interval": "10s" │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ 方式二:配置文件注册 │ │
│ │ /etc/consul.d/order-service.json │ │
│ │ 适合非侵入式注册(第三方服务) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. 服务发现(Service Discovery) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DNS 接口:order-service.service.consul │ │
│ │ HTTP API:GET /v1/health/service/order-service │ │
│ │ → 返回所有健康的实例列表(IP:Port + 元数据) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. 健康检查(Health Checking) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 见 3.3 节详述 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 4. KV Store(键值存储) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 存储在 Raft 集群中,强一致性 │ │
│ │ 用途:动态配置、分布式锁、Leader 选举 │ │
│ │ PUT /v1/kv/config/db/timeout → "30s" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 5. 多数据中心(Multi-Datacenter) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ WAN Gossip 协议跨数据中心同步服务目录 │ │
│ │ 支持异地多活和灾备场景 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.3 健康检查的三种方式
┌─────────────────────────────────────────────────────────────┐
│ 健康检查三种方式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 方式一:TTL Check(心跳上报) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 服务主动调用 Consul Agent API 报告"我还活着" │ │
│ │ PUT /v1/agent/check/pass/service:order-service │ │
│ │ • 服务需要在 TTL 超时前上报 │ │
│ │ • 不依赖网络可达性(服务主动推) │ │
│ │ • 缺点:需要服务代码感知健康检查 │ │
│ │ • 适用:无法从外部探测的服务(如 Worker 进程) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方式二:HTTP Check(HTTP 端点检测) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Consul Agent 定期 GET /health → 期望 2xx │ │
│ │ "Check": { │ │
│ │ "HTTP": "http://localhost:3001/health", │ │
│ │ "Interval": "10s", │ │
│ │ "Timeout": "1s" │ │
│ │ } │ │
│ │ • 最常用的方式 │ │
│ │ • 可在端点中检查依赖(DB/Redis 连通性) │ │
│ │ • 缺点:健康端点的实现质量参差不齐 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方式三:gRPC Check(gRPC 健康协议) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Consul Agent 调用 gRPC Health Checking Protocol │ │
│ │ • gRPC 标准健康检查协议(grpc.health.v1.Health) │ │
│ │ • 与 K8s gRPC 健康探针兼容 │ │
│ │ • 适用:gRPC 服务 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 健康检查设计原则: │
│ • 存活检查(Liveness):进程还活着吗?→ 轻量返回 200 │
│ • 就绪检查(Readiness):可以接收流量了吗?→ 检查依赖 │
│ • 不要把存活检查和就绪检查混在一个端点 │
│ • 健康端点不要有副作用(幂等、不写数据库、不调用外部) │
│ │
└─────────────────────────────────────────────────────────────┘第4部分:服务注册与负载均衡
4.1 服务注册的生命周期
┌─────────────────────────────────────────────────────────────┐
│ 服务注册生命周期 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 启动 │────▶│ 健康 │────▶│ 停止 │ │
│ │ (Starting)│ │ (Healthy)│ │ (Stopping)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 注册服务 接收流量 注销服务 │
│ + 初始健康 定期报告 优雅下线 │
│ 检查 (TTL/HTTP) (Deregister) │
│ │
│ 关键时序: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ T0: 服务进程启动 │ │
│ │ T1: 加载配置 → 初始化 DB 连接 → 启动 HTTP Server │ │
│ │ T2: 注册到 Consul(此时 /health 已经可用) │ │
│ │ T3: 健康检查首次通过 → 状态变为 Passing │ │
│ │ T4: 开始接收流量 │ │
│ │ ... │ │
│ │ T5: 收到 SIGTERM → 从 Consul 注销 │ │
│ │ T6: 等待已有请求处理完(graceful shutdown) │ │
│ │ T7: 进程退出 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 常见坑: │
│ • T1-T2 之间的窗口:服务已启动但未注册 → 发现不到 │
│ • T5-T6 之间的窗口:已注销但请求可能还在路上 → 请求失败 │
│ • 解决:启动延迟(initial_delay)+ 优雅停机(drain) │
│ │
└─────────────────────────────────────────────────────────────┘4.2 客户端负载均衡策略
┌─────────────────────────────────────────────────────────────┐
│ 负载均衡策略对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 策略 │ 原理 │ 适用场景 │
│ ├──────────────┼──────────────────┼────────────────────┤
│ │ 轮询 │ 按顺序分配 │ 后端性能一致 │
│ │ Round-Robin │ A→B→C→A→B→C │ 无状态服务 │
│ ├──────────────┼──────────────────┼────────────────────┤
│ │ 随机 │ 随机选一个 │ 简单、性能好 │
│ │ Random │ 大量请求 → 均匀 │ 通用场景 │
│ ├──────────────┼──────────────────┼────────────────────┤
│ │ 最少连接 │ 选活跃连接最少的 │ 请求耗时差异大 │
│ │ Least Conn │ │ 长连接场景 │
│ ├──────────────┼──────────────────┼────────────────────┤
│ │ 一致性哈希 │ 相同 key → 同一台 │ 有状态服务 │
│ │ Consistent │ 节点增减时最小 │ 缓存/会话保持 │
│ │ Hash │ 化重新分配 │ │
│ ├──────────────┼──────────────────┼────────────────────┤
│ │ 加权 │ 按权重分配 │ 后端性能不均 │
│ │ Weighted │ 性能好的权重高 │ 异构部署 │
│ │
│ 一致性哈希的核心价值: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 传统哈希:hash(key) % N (N=节点数) │ │
│ │ 节点从 3 → 4(扩容)→ 几乎所有 key 的映射都变了 │ │
│ │ → 缓存全部失效,引发缓存雪崩 │ │
│ │ │ │
│ │ 一致性哈希:key 被映射到一个环上 │ │
│ │ 节点也在环上有位置 │ │
│ │ 节点从 3 → 4 → 只有约 1/4 的 key 需要重新分配 │ │
│ │ → 缓存命中率保持高位 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第5部分:K8s Service Discovery
5.1 K8s 服务发现全景
┌─────────────────────────────────────────────────────────────┐
│ K8s 服务发现架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 外部流量 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Service (ClusterIP: 10.96.0.10:80) │ │ │
│ │ │ type: ClusterIP │ │ │
│ │ │ selector: app=order-service │ │ │
│ │ └──────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ EndpointSlice │ │ │
│ │ │ [10.1.0.5:3001, 10.1.0.6:3001, ...] │ │ │
│ │ └──────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────┼──────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Pod 1 │ │ Pod 2 │ │ Pod 3 │ │ │
│ │ │ 10.1.0.5 │ │ 10.1.0.6 │ │ 10.1.0.7 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 数据流: │
│ 1. kube-apiserver 持续同步 Service + EndpointSlice │
│ 2. kube-proxy 监听 API Server,更新 iptables/IPVS 规则 │
│ 3. CoreDNS 解析 service-name.namespace.svc.cluster.local │
│ 4. Pod 内的请求通过 iptables/IPVS 规则转发到目标 Pod │
│ │
└─────────────────────────────────────────────────────────────┘5.2 ClusterIP vs Headless Service
┌─────────────────────────────────────────────────────────────┐
│ ClusterIP vs Headless │
├─────────────────────────────────────────────────────────────┤
│ │
│ ClusterIP Service(默认模式): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ apiVersion: v1 │ │
│ │ kind: Service │ │
│ │ spec: │ │
│ │ type: ClusterIP ← 分配虚拟 IP │ │
│ │ selector: │ │
│ │ app: order-service │ │
│ │ ports: │ │
│ │ - port: 80 │ │
│ │ targetPort: 3001 │ │
│ │ │ │
│ │ DNS 解析:order-service → 10.96.0.10(ClusterIP) │ │
│ │ 流量路径:Pod → ClusterIP → kube-proxy → 目标 Pod │ │
│ │ • 有虚拟 IP,自动负载均衡 │ │
│ │ • 适合:无状态服务的常规访问 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Headless Service(无头服务): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ apiVersion: v1 │ │
│ │ kind: Service │ │
│ │ spec: │ │
│ │ clusterIP: None ← 不分配虚拟 IP │ │
│ │ selector: │ │
│ │ app: order-service │ │
│ │ │ │
│ │ DNS 解析:order-service → [10.1.0.5, 10.1.0.6, ...] │ │
│ │ 流量路径:Pod 直接连接目标 Pod(无代理) │ │
│ │ • 返回所有 Pod IP(不是 VIP) │ │
│ │ • 客户端需要自己负载均衡 │ │
│ │ • 适合:StatefulSet、gRPC 长连接、自定义负载均衡 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 什么时候用 Headless: │
│ • StatefulSet:每个 Pod 需要稳定的网络标识 │
│ → pod-0.order-service.namespace.svc.cluster.local │
│ • gRPC 连接池:需要绕过 kube-proxy 建立长连接 │
│ • 自定义服务发现:如集成 Consul 的客户端负载均衡 │
│ │
└─────────────────────────────────────────────────────────────┘5.3 CoreDNS 与 EndpointSlice
┌─────────────────────────────────────────────────────────────┐
│ CoreDNS 工作机制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ CoreDNS = K8s 集群的 DNS 服务器 + 服务发现入口 │
│ │
│ 解析流程: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Pod A → DNS 查询 "order-service.default.svc" │ │
│ │ → CoreDNS(监听 API Server) │ │
│ │ → 查 Service 对象 │ │
│ │ → ClusterIP? → 返回 10.96.0.10 │ │
│ │ → Headless? → 查询 EndpointSlice │ │
│ │ → 返回 Pod IP 列表 [10.1.0.5, 10.1.0.6] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ CoreDNS 的局限(DNS TTL 问题): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 旧的 Endpoints 对象:每个 Service 一个对象 │ │
│ │ Pod 数 > 1000 时,Endpoints 对象巨大(>1MB) │ │
│ │ 任何 Pod 变更 → 整个对象更新 → 全量推送 │ │
│ │ │ │
│ │ • 新的 EndpointSlice(K8s 1.21+ 默认开启): │ │
│ │ 将 Endpoints 分片(每片最多 100 个地址) │ │
│ │ Pod 变更 → 只更新变更的那一片 │ │
│ │ 网络传输量减少 10-100 倍 │ │
│ │ 大规模集群(5000+ Pod)的性能基础 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第6部分:Consul vs Etcd vs Eureka vs Nacos vs Zookeeper
6.1 五方横向对比
┌─────────────────────────────────────────────────────────────┐
│ 服务发现组件对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 维度 │ Consul │ Etcd │ Eureka │ Nacos │ ZK │
│ ├──────────┼─────────┼────────┼────────┼────────┼─────┤
│ │ 一致性 │ Raft │ Raft │ AP(最终)│ Raft │ ZAB│
│ │ 协议 │ │ │ │ │ │
│ ├──────────┼─────────┼────────┼────────┼────────┼─────┤
│ │ 健康检查 │ ✅ 丰富 │ ❌ 无 │ ✅ 心跳 │ ✅ 丰富 │ ❌ │
│ │ │ TTL/HTTP│ 需上层 │ 只能TTL │ TTL/HTTP│ Session│
│ │ │ /gRPC │ 实现 │ │ /MySQL │ 机制│
│ ├──────────┼─────────┼────────┼────────┼────────┼─────┤
│ │ KV Store │ ✅ │ ✅ │ ❌ │ ✅ │ ✅│
│ ├──────────┼─────────┼────────┼────────┼────────┼─────┤
│ │ 多数据中心│ ✅ │ ❌ │ ✅ │ ✅ │ ❌│
│ ├──────────┼─────────┼────────┼────────┼────────┼─────┤
│ │ DNS 接口 │ ✅ │ ❌ │ ❌ │ ❌ │ ❌│
│ ├──────────┼─────────┼────────┼────────┼────────┼─────┤
│ │ 配置中心 │ KV 可做 │ KV 可做│ ❌ │ ✅ 原生 │ ❌│
│ │ │ 不专业 │ 不专业 │ │ │ │
│ ├──────────┼─────────┼────────┼────────┼────────┼─────┤
│ │ 语言 │ Go │ Go │ Java │ Java │Java│
│ ├──────────┼─────────┼────────┼────────┼────────┼─────┤
│ │ 最佳场景 │ 多DC/ │ K8s │ Spring │ 阿里系 │ 旧项│
│ │ │ 异构环境 │ 存储 │ Cloud │ 微服务 │ 目 │
│ │
│ 选型决策树: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 你已经在用 K8s 了吗? │ │
│ │ → 是:直接用 CoreDNS + Service,不需要额外组件 │ │
│ │ → 否:继续往下看 │ │
│ │ │ │
│ │ 2. 你是纯 Spring Cloud 技术栈吗? │ │
│ │ → 是:Eureka(集成最简单)或 Nacos(功能更全) │ │
│ │ → 否:继续往下看 │ │
│ │ │ │
│ │ 3. 你需要多数据中心或者 DNS 接口吗? │ │
│ │ → 是:Consul(唯一支持 DNS + 多 DC 的方案) │ │
│ │ → 否:继续往下看 │ │
│ │ │ │
│ │ 4. 你只需要一个分布式 KV/锁/选举吗? │ │
│ │ → 是:Etcd(K8s 也在用它,生态最成熟) │ │
│ │ → 否:Nacos(服务发现 + 配置中心二合一) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘6.2 CAP 视角下的服务发现
┌─────────────────────────────────────────────────────────────┐
│ CAP 与注册中心选型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 服务发现本质上是一个"一致性 vs 可用性"的权衡问题。 │
│ │
│ CP 系统(Consul / Etcd / Zookeeper / Nacos): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 一致性优先(Raft / ZAB 共识协议) │ │
│ │ • 保证:所有节点看到的服务列表一致 │ │
│ │ • 代价:Leader 选举期间不可用(~1-5s) │ │
│ │ • 影响:这段时间内新的注册/注销被阻塞 │ │
│ │ • 风险评估:服务注册的"短暂不可用"通常可以接受 │ │
│ │ • 极少数场景需要担心(如秒级扩缩容) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ AP 系统(Eureka): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 可用性优先(最终一致性) │ │
│ │ • 保证:注册中心永远可用(无 Leader 选举) │ │
│ │ • 代价:不同节点看到的服务列表可能短暂不一致 │ │
│ │ • 影响:可能调用到已下线的实例(靠客户端重试兜底) │ │
│ │ • 设计哲学:宁可调到一个死实例再重试, │ │
│ │ 也不要因为 Leader 选举而完全不可用 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 实践结论: │
│ 对于绝大多数场景,CP 型注册中心(Consul/Nacos)的短暂 │
│ 不可用是可以接受的。Eureka 选择 AP 更多是历史原因 │
│ (Netflix 的极端弹性需求)。如果你不是 Netflix 级别的 │
│ 规模,不需要为 AP 牺牲一致性。 │
│ │
└─────────────────────────────────────────────────────────────┘核心总结
总结1:服务发现解决的是"动态地址"的问题
在微服务中,IP 和端口不是固定的。服务发现让你用"服务名"来引用一个服务,而不是"IP:Port"。底层机制负责把服务名解析到当前可用的实例列表。
总结2:三种模式各有适用场景
DNS-based 最通用但故障感知慢,Client-side 最灵活但需要 SDK 绑定,Server-side 对客户端最友好但多一跳延迟。在 K8s 上,CoreDNS + Service 已经解决了 90% 的问题。
总结3:健康检查是服务发现的灵魂
注册只是第一步,没有健康检查的服务发现形同虚设——你不知道注册的那个实例还活着吗。生产环境必须同时配置存活检查和就绪检查,避免流量打到不健康的实例。
总结4:CAP 的权衡在实践中没那么纠结
Consul/Nacos 的 Raft 领导选举通常只需要 1-5 秒,对于服务发现场景完全可接受。不要因为"CP 系统在分区时不可用"而选 AP 系统——你遇到网络分区的概率远低于遇到"将流量打到下线实例"的概率。
章节测试
测试1:服务发现解决的根本问题是什么?
A. 让服务之间能够找到彼此,即使 IP 地址会动态变化 B. 提高服务间通信的吞吐量 C. 降低网络延迟 D. 统一不同编程语言之间的通信协议
测试2:DNS-based 服务发现的最大缺点是什么?
测试3:Consul 的 Agent 和 Server 的职责分别是什么?
测试4:TTL Check 和 HTTP Check 分别适合什么场景?
测试5:K8s 中 ClusterIP Service 和 Headless Service 的核心区别是什么?
测试6:Spring Cloud 微服务项目,目前使用 Eureka 做服务发现,想引入配置中心功能。以下哪个迁移方案最合理?
A. 保留 Eureka + 新部署 Consul B. 保留 Eureka + 新部署 Apollo C. 将 Eureka 替换为 Nacos(服务发现 + 配置中心二合一) D. 保留 Eureka + 用 Etcd 做配置中心
参考答案
测试1答案
答案:A。服务发现的核心价值是让调用方能用"服务名"而不是"IP:Port"来引用目标服务,从而应对容器化部署中频繁变化的 IP 地址和动态扩缩容。
测试2答案
DNS-based 服务发现的最大缺点:故障感知慢。DNS 有 TTL 缓存机制,当某个服务实例宕机后,客户端可能还在用缓存的 DNS 结果(包含已宕机的 IP),直到 TTL 过期才会重新查询。这个延迟通常是 30 秒到 5 分钟,在这期间请求会失败。
测试3答案
Consul Server:存储所有状态(服务注册信息、健康状态、KV 数据),运行 Raft 共识协议,是集群的"大脑"。通常部署 3-5 个节点形成高可用集群。
Consul Agent:每个物理/虚拟节点上部署一个,负责执行健康检查(主动探测服务)、提供 DNS 查询接口、转发写请求到 Server。Agent 是无状态的,挂了不影响数据。
测试4答案
TTL Check:适合无法从外部探测的服务,如 Worker 进程、定时任务。服务需要在 TTL 过期前主动向 Consul Agent 报告"我还活着"。缺点是需要在服务代码中集成心跳上报逻辑。
HTTP Check:适合有 HTTP 端点的服务(Web 服务、API 服务)。Consul Agent 定期 GET 健康端点,不需要修改业务代码。最常用的方式。
测试5答案
ClusterIP Service:分配一个虚拟 IP,kube-proxy 负责将访问 ClusterIP 的流量负载均衡到后端 Pod。客户端不需要知道 Pod 的真实 IP。适合无状态服务的常规访问。
Headless Service:不分配虚拟 IP,DNS 直接返回所有后端 Pod 的 IP 列表。客户端需要自己实现负载均衡逻辑。适合 StatefulSet(需要稳定网络标识)和需要绕过 kube-proxy 的场景(如 gRPC 长连接)。
测试6答案
答案:C。Nacos 天然支持服务发现和配置中心,可以一次性替换 Eureka 并补齐配置中心功能。减少了运维两个独立组件的成本。而且 Nacos 与 Spring Cloud 有良好的集成(spring-cloud-starter-alibaba-nacos-discovery 和 spring-cloud-starter-alibaba-nacos-config)。
相关笔记
- [[00-overview]] — 基础设施组件全景
- [[01-nginx-and-reverse-proxy]] — Nginx 与反向代理
- [[03-configuration-center]] — 配置中心深入
- [[../00-overview]] — 中间件全景总览
下一步学习
- [ ] 阅读 03 - 配置中心 — 理解配置管理的演进与最佳实践
- [ ] 在你的开发环境中搭建一个 Consul 单节点,体验服务注册与发现
- [ ] 对比你当前项目的服务发现方案,评估是否有改进空间
- [ ] 如果你在使用 K8s,验证 CoreDNS + Service 的 DNS 解析流程
学习状态:🟡 开始学习