Files
autumn-recruitment/05.架构/service-governance/服务注册与发现.md
T

194 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [arch/microservice, service-discovery, eureka, nacos, consul, cap-theory, health-check]
create time: 2026-08-08 18:00
update time: 2026-08-08 18:00
---
# 服务注册与发现
## 概述
服务注册与发现是微服务架构的基石。当服务实例动态扩缩容时,调用方需要知道"当前谁在提供服务"以及"它们在哪"。本文对比 Eureka、Nacos、Consul 三大主流方案的核心设计差异,分析 CAP 理论在服务注册中心中的取舍,以及健康检查的各种实现方式。
## 核心原理
### 服务注册/注销/续约流程
```mermaid
sequenceDiagram
participant Client as 客户端
participant Server as 服务提供者
participant Registry as 注册中心
participant Discover as 消费方
Client->>Server: 启动服务
Server->>Registry: 1. 注册服务 (POST /register)
Registry-->>Server: 注册成功
Server->>Registry: 2. 心跳续约 (POST /heartbeat)
Registry->>Registry: 更新 lastDirtyTimestamp
loop 每 N 秒
Server->>Registry: 心跳续约
Registry-->>Server: ACK
end
Discover->>Registry: 3. 查询服务列表 (GET /instances)
Registry-->>Discover: 返回健康实例列表
Discover->>Server: 发起 RPC 调用
Note over Server: 服务关闭
Server->>Registry: 4. 主动注销 (DELETE /deregister)
Registry-->>Server: 注销成功
Registry->>Registry: 从服务列表中移除
```
**三步走**:注册 → 续约(或心跳)→ 注销。绝大多数故障场景下,调用方感知到服务宕机会延迟一个续约超时周期(通常 30~90 秒)。
### Eureka — AP 取向的自我修复
Eureka 是 Netflix 开源的方案,设计上严格遵循 **AP(可用性 + 分区容错性)**:
- **双副本架构**:Peer Replication 机制将数据异步同步给所有节点,不同步视为正常而非故障。
- **自我保护机制**:当 15 分钟内超过 85% 实例未发送心跳,Eureka 锁定保护窗口,不再剔除任何实例。这意味着被误判为宕机的实例仍会出现在服务列表中——宁可多调几次再失败,也不主动剔除。
- **客户端缓存**:消费者缓存服务列表(默认每 30 秒刷新),即使注册中心完全不可用,消费方依然能根据本地缓存继续通信。
```java
// Eureka 自我保护机制触发条件
public boolean shouldEnableRenewments() {
double threshold = registry.getRenewalThreshold(); // 85%
if (serverConfig.isSelfPreservationEnabled()
&& registry.getCachedInstanceStatus().equals(HEAVY_OVERLOAD)) {
return true; // 进入自我保护,允许续约
}
return super.shouldEnableRenewments();
}
```
> [!WARNING]
> 自我保护机制在生产环境是个双刃剑:它保证了高可用,但可能导致流量打向已宕机的实例。建议监控 `eureka.server.selfPreservationThreshold` 指标。
### Nacos — CP/AP 可切换的统一平台
Nacos 解决了 Eureka 和 ZooKeeper 只能二选一的问题:
| 模式 | 适用场景 | 一致性协议 | 典型部署形态 |
|------|---------|-----------|------------|
| AP 模式 | 服务注册发现 | Distro 协议(自研,类似 Eureka 的双写) | 集群部署 |
| CP 模式 | 配置管理 | Raft 协议 | 奇数节点集群 |
**统一命名空间管理**:Nacos 通过 `namespace` 实现环境隔离(dev/test/prod),每个命名空间有独立的服务和配置视图。底层基于 PostgreSQL 存储元数据,天然支持持久化。
```go
// Nacos Go SDK 服务订阅示例
client, _ := nacos.NewClient(nacos.Config{
ServerAddr: "127.0.0.1:8848",
NamespaceId: "prod-ns",
})
info, _ := client.Subscribe(&nacos.Subscriber{
ServiceName: "user-service",
Cluster: "DEFAULT",
OnEvent: func(event *nacos.Event) {
for _, inst := range event.Hosts {
fmt.Println(inst.InstanceIp, inst.InstancePort)
}
},
})
```
> [!TIP]
> 面试常考点:为什么 Nacos 能在同一套代码中既做 AP 又做 CP?答:两种模式使用不同的数据存储路径和服务类型——`nacos.v1.auth.user`(CP,Raft)与 `nacos.v1.instance`(AP,Distro)。
### Consul — Serf Gossip + 丰富健康检查
HashiCorp 出品的 Consul 有三个独特优势:
1. **Serf gossip 协议**:基于 WAN Gossip 的成员管理,相比 TCP 直连更容错。节点通过 pull-based push-based 混合机制传播成员变更。
2. **丰富的健康检查**:支持 TCP、HTTP、HTTP Body、docker、脚本五种检查方式。还支持 TTL(手动报告状态)模式。
3. **KV 存储能力**:内嵌 Raft 一致性 KV 存储,可用于配置管理、分布式锁等辅助场景。
```go
// Consul HTTP 健康检查配置
func registerCheck() *api.AgentServiceCheck {
return &api.AgentServiceCheck{
HTTP: "http://localhost:8080/health",
Timeout: "5s",
Interval: "10s",
DeregisterCriticalServiceAfter: "90s",
}
}
```
### CAP 理论在服务注册中心的体现
| 注册中心 | CAP 选择 | 网络分区时的行为 | 数据一致性保证 |
|---------|---------|-----------------|--------------|
| Eureka | AP | 各节点独立响应,可能返回过期数据 | 最终一致 |
| Nacos (AP) | AP | Distro 协议异步复制,容忍短暂不一致 | 最终一致 |
| Nacos (CP) | CP | 选出 Leader 后响应,部分节点不可用时降级 | 强一致 |
| Consul | CP(TCP)/ AP(DNS) | Raft Leader 不可达时无法写入 | 读可能 stale |
> [!NOTE]
> PACELC 补充:CAP 只讨论网络分区场景。PACELC 指出:无分区时,延迟(Latency)与一致性(Consistency)仍需权衡。Nacos AP 模式、Eureka 都是选择了低延迟而非强一致。
### 健康检查方式对比
| 方式 | 原理 | 优点 | 缺点 |
|------|-----|------|------|
| TCP | 尝试建立 TCP 连接 | 轻量、通用 | 无法感知应用层故障 |
| HTTP/HTTPS | 请求指定 endpoint 检查状态码 | 灵活,可携带业务逻辑 | 增加服务器负载 |
| gRPC | gRPC Health Checking Protocol | 适合 gRPC 服务 | 需额外 proto 定义 |
| Script | 执行自定义脚本 | 最灵活,可做深度检查 | 维护成本高 |
| Edge探测 | 模拟真实用户请求 | 最贴近用户体验 | 复杂度高,一般用于 SLA 监控 |
## 代码示例
以下是一个简化版的 Go 服务注册中心心跳续约逻辑:
```go
type Heartbeat struct {
ServiceID string
InstanceID string
LastPingAt time.Time
}
type Registry struct {
instances map[string]*Instance // serviceID -> Instance
mu sync.RWMutex
}
// Register 注册一个新实例
func (r *Registry) Register(svc, instanceID, addr string) error {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.instances[svc]; !ok {
r.instances[svc] = &Instance{Addr: addr}
}
return nil
}
// Renew 续约:更新最后心跳时间
func (r *Registry) Renew(svc, instanceID string) bool {
r.mu.Lock()
defer r.mu.Unlock()
inst, ok := r.instances[svc]
if !ok {
return false
}
inst.LastHeartbeat = time.Now()
return true
}
```
## 实践场景
**秋招高频问题:**
- "Eureka 的自我保护机制什么时候该关?" — 答案:金融类对一致性要求高的场景可以考虑关闭,普通互联网业务不建议关。关了之后可能出现"雪崩式下线"——一批实例同时被认为是故障而全部剔除。
- "Nacos 和 Eureka 有什么本质区别?" — Eureka 只管服务发现,Nacos 是一体化平台(服务发现 + 配置中心)。Nacos 还能按 namespace/group/key 精确分组配置。
- "Consul 的 DNS 接口为什么是 AP 模式?" — DNS 本身是无状态的,Consul 的 DNS API 直接指向随机节点读取本地内存缓存,不经过 Raft 日志,因此速度极快但可能读到旧数据。
**实战建议:** 小团队用一个 Nacos 集群就够用;大规模多机房场景考虑 Consul 的多数据中心能力。
## 关联笔记
- [[负载均衡算法]]
- [[限流熔断降级]]
- [[配置中心设计]]