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

194 lines
8.0 KiB
Markdown
Raw Normal View History

2026-08-08 19:01:04 +08:00
---
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 的多数据中心能力。
## 关联笔记
- [[负载均衡算法]]
- [[限流熔断降级]]
- [[配置中心设计]]