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