Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

中间件 / Middleware

1. Web 中间件全景 / The Web Middleware Landscape

2. 中间件——流量洪峰下的系统保护 / Middleware for Protecting Systems Under Traffic Spikes

cache layer

1. 缓存架构全景 / Cache Architecture Overview

2. Redis 深入——为什么你的缓存总是出问题 / Redis Internals and Cache Failure Modes

3. Redis 数据结构场景应用——什么时候用什么 / Choosing Redis Data Structures for Real Applications

4. 缓存策略——库存变了,缓存怎么处理 / Cache Strategies and Inventory Consistency

5. Redis 缓存三剑客——穿透/击穿/雪崩 + 一致性策略 / Redis Cache Penetration, Breakdown, Avalanche, and Consistency

6. Redis 分布式锁——从 SETNX 到 Redisson / Redis Distributed Locks from SETNX to Redisson

7. Redis Cluster 与 Sentinel 高可用架构 / Redis Cluster and Sentinel High-Availability Architecture

8. Redis 高级特性——Stream / PubSub / Module / LLM 应用

9. Redis 架构深度分析——为什么 Redis 能这么快 / Redis Architecture and the Sources of Its Performance

10. Redis 全景——为什么你的系统需要一个缓存层 / The Redis Landscape and Why Systems Need a Cache Layer

message queue

1. 消息队列全景——为什么你的系统需要一个中间人 / Message Queue Overview and Why Your System Needs a Middleman

2. Kafka 核心——为什么你的消息总是"丢"了 / Kafka Fundamentals and Message Delivery Semantics

3. 消息队列高级——死信队列、延迟消息、消息积压 / Advanced Messaging with Dead Letters, Delays, and Backlogs

4. Kafka Streams 与 Connect —— 让数据自己流动起来 / Kafka Streams and Connect for Streaming Data Pipelines

5. RabbitMQ 深度解析 —— 灵活路由与消息可靠性 / RabbitMQ Deep Dive into Flexible Routing and Reliability

6. 消息队列对比——为什么最终选了 Kafka / Comparing Message Queues and Choosing Kafka

search engine

1. 搜索引擎知识体系 / Search Engine Knowledge System

2. Elasticsearch——为什么 Like 查询总是那么慢 / Elasticsearch for Full-Text Search at Scale

3. Elasticsearch 查询 DSL 深入——为什么你的搜索总是不准 / Elasticsearch Query DSL Deep Dive

4. Elasticsearch 集群规划与运维——为什么你的集群总是"黄" / Elasticsearch Cluster Planning and Operations

5. Meilisearch 与轻量搜索替代方案——当 ES 太重时 / Meilisearch and Lightweight Search Alternatives

infrastructure

1. 基础设施组件全景 / Infrastructure Components Landscape

2. Nginx 与反向代理 / Nginx and Reverse Proxy

3. 服务发现 / Service Discovery

4. 配置中心 / Configuration Center

本页目录

服务发现 / 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

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),    │
│  必须引入自动化的服务发现机制。                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

第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)           │   │
│  │  (客户端)          │                                 │   │
│  │                    ├──查询注册中心──▶ 健康实例列表    │   │
│  │                    │                                 │   │
│  │                    └──选择实例并转发──▶ 订单服务      │   │
│  │                                                     │   │
│  │  ★ 客户端零感知,多一跳网络延迟                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

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)       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

第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 是无状态的,挂了不影响数据               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

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 协议跨数据中心同步服务目录               │   │
│  │  支持异地多活和灾备场景                               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

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):可以接收流量了吗?→ 检查依赖     │
│  • 不要把存活检查和就绪检查混在一个端点                      │
│  • 健康端点不要有副作用(幂等、不写数据库、不调用外部)      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

第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)      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

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 需要重新分配      │   │
│  │  → 缓存命中率保持高位                                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

第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        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

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 的客户端负载均衡          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

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)的性能基础                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

第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(服务发现 + 配置中心二合一)         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

核心总结 ​

总结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 解析流程

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇2. Nginx 与反向代理 / Nginx and Reverse Proxy
下一篇4. 配置中心 / Configuration Center

持续记录,持续成长

Copyright © Tidenflow