Appearance
Eureka 详解:服务注册与发现
Eureka 是 Netflix 开源的服务注册与发现(Service Registry & Discovery)组件,是 Spring Cloud Netflix 生态中最核心的基础设施之一。在 Nacos 成为主流之前,Eureka 几乎是 Spring Cloud 微服务架构中注册中心的唯一选择。尽管 Eureka 2.0 已停止维护,但其设计思想——特别是 CAP 理论中的 AP 模型和自我保护机制——至今仍是分布式系统领域的经典案例。
一、为什么需要 Eureka?
1.1 没有注册中心时的问题
在微服务架构中,服务实例的数量和网络位置是动态变化的。如果服务之间通过硬编码的 IP + 端口来调用,每一次实例变化都意味着配置修改和重新部署。
没有注册中心时:
order-service 需要调用 user-service
order-service 的 application.yml:
user-service:
urls:
- http://192.168.1.10:8080
- http://192.168.1.11:8080
- http://192.168.1.12:8080 ← 硬编码 IP,实例变了就得改配置
问题:
1. user-service 扩容到 5 个实例 → 改配置,重启 order-service
2. 192.168.1.10 宕机 → order-service 仍然会尝试调用,直到超时
3. 微服务数量增加 → 每对调用关系都要维护一堆 IP 列表
4. 无法动态感知实例上下线
5. 服务间调用关系混乱,无法追踪1.2 Eureka 解决的问题
有了 Eureka 注册中心后:
order-service 调用 user-service 时,只需要知道服务名:
order-service:
@FeignClient(name = "user-service") ← 只写服务名,不写 IP
UserServiceClient userServiceClient;
Eureka Server 注册表:
┌─────────────────────────────────────────────┐
│ user-service 注册表 │
│ ┌──────────────────────────────────────┐ │
│ │ 192.168.1.10:8080 UP (healthy) │ │
│ │ 192.168.1.11:8080 UP (healthy) │ │
│ │ 192.168.1.12:8080 UP (healthy) │ │
│ │ 192.168.1.13:8080 DOWN (unhealthy) │ │ ← 不健康,不返回
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
1. 扩容 → 新实例自动注册到 Eureka,调用方自动感知
2. 宕机 → 心跳超时,Eureka 自动剔除
3. 调用 → 从 Eureka 拿到健康实例列表,负载均衡选一个
4. 无需硬编码任何 IPEureka 的核心价值:
| 价值 | 说明 |
|---|---|
| 服务注册 | 服务启动时自动注册到 Eureka Server,无需手动配置 |
| 服务发现 | 消费者通过服务名即可获取提供者的实例列表 |
| 健康检查 | 心跳机制自动剔除不健康实例,保证调用成功率 |
| 动态感知 | 实例上下线实时感知,自动更新调用列表 |
| 负载均衡配合 | 与 Ribbon/LoadBalancer 配合,实现客户端负载均衡 |
| 高可用 | 自我保护机制 + 集群对等复制,保证注册中心不宕 |
二、核心架构
2.1 整体架构图
┌──────────────────────────────────────────────────────────────────────┐
│ Eureka 整体架构 │
│ │
│ ┌─────────────────────┐ │
│ │ Eureka Server │ ← 注册中心(服务端) │
│ │ ┌───────────────┐ │ │
│ │ │ 服务注册表 │ │ 存储所有服务实例信息 │
│ │ │ ┌─────────┐ │ │ (InstanceInfo + LeaseInfo) │
│ │ │ │ Lease 1 │ │ │ │
│ │ │ │ Lease 2 │ │ │ │
│ │ │ │ Lease 3 │ │ │ │
│ │ │ │ ... │ │ │ │
│ │ │ └─────────┘ │ │ │
│ │ └───────────────┘ │ │
│ │ 自我保护机制 │ ← 15min 内心跳丢失 >85% 时触发 │
│ │ 集群对等复制 │ ← Peer-to-Peer 对等架构 │
│ └─────────┬───────────┘ │
│ │ │
│ ┌────────┴────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 服务提供者 │ │ 服务消费者 │ │
│ │ (Provider) │ │ (Consumer) │ │
│ │ │ │ │ │
│ │ Eureka Client │ │ Eureka Client │ │
│ │ │ │ │ │
│ │ 1. 注册(Register)│ │ 3. 获取注册表 │ │
│ │ 2. 续约(Renew) │ │ (Fetch) │ │
│ │ 4. 注销(Cancel) │ │ 5. 缓存到本地 │ │
│ │ │ │ │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ 6. 通过服务名调用提供者 │
│ 7. Ribbon 客户端负载均衡 │
└──────────────────────────────────────────────────────────────────────┘2.2 三大角色
| 角色 | 职责 | 关键行为 |
|---|---|---|
| Eureka Server(注册中心) | 存储服务注册表,管理服务实例的注册、续约、注销 | 维护 Leases 表,运行自我保护机制,集群间对等复制 |
| Service Provider(服务提供者) | 向 Eureka Server 注册自身,提供业务服务 | 启动时注册,定时发送心跳续约,关闭时注销 |
| Service Consumer(服务消费者) | 从 Eureka Server 获取服务列表,调用服务提供者 | 定时拉取注册表,缓存到本地,通过服务名发起调用 |
2.3 核心概念
| 概念 | 说明 |
|---|---|
| Register(注册) | 服务提供者启动时,向 Eureka Server 注册自己的 IP、端口、服务名等元数据 |
| Renew(续约/心跳) | 服务提供者每 30 秒向 Eureka Server 发送一次心跳,告诉 Server "我还活着" |
| Fetch Registry(获取注册表) | 服务消费者每 30 秒从 Eureka Server 拉取一份最新的服务注册表,并缓存到本地 |
| Cancel(注销) | 服务提供者正常关闭时,向 Eureka Server 发送注销请求,从注册表中移除 |
| Eviction(剔除) | 当服务提供者超过 90 秒未发送心跳,Eureka Server 将其从注册表中剔除 |
| Self Preservation(自我保护) | 当 15 分钟内心跳丢失比例超过 85%,Eureka 认为这是网络问题而非服务宕机,不剔除任何实例 |
三、服务注册与发现流程
3.1 完整时序图
服务提供者 (Provider) Eureka Server 服务消费者 (Consumer)
│ │ │
│ 1. 启动时注册 │ │
│ POST /eureka/apps │ │
│ ──────────────────────► │ │
│ │ │
│ 2. 返回注册成功 204 │ │
│ ◄────────────────────── │ │
│ │ │
│ │ 3. 启动时拉取注册表 │
│ │ GET /eureka/apps │
│ │ ◄─────────────────────── │
│ │ │
│ │ 4. 返回全量注册表 │
│ │ ───────────────────────► │
│ │ (缓存到本地) │
│ │ │
│ 5. 心跳续约 (每 30s) │ │
│ PUT /eureka/apps/{id} │ │
│ ──────────────────────► │ │
│ │ │
│ 6. 续约成功 200 │ │
│ ◄────────────────────── │ │
│ │ 7. 增量/全量拉取(每 30s) │
│ │ GET /eureka/apps/delta │
│ │ ◄─────────────────────── │
│ │ │
│ │ 8. 返回变更的注册信息 │
│ │ ───────────────────────► │
│ │ │
│ │ │
│ ... 循环 5-8 步骤 ... │ │
│ │ │
│ │ 9. 调用服务 │
│ │ (通过服务名,如 │
│ │ user-service) │
│ ◄──────────────────────────────────────────────── │
│ │ │
│ 10. 服务正常关闭 │ │
│ DELETE /eureka/apps │ │
│ ──────────────────────► │ │
│ │ │3.2 注册流程详解
第一步:服务注册(Register)
服务提供者启动时,Eureka Client 会向 Eureka Server 发送 REST 请求注册自身。注册信息包括:服务名、IP 地址、端口号、健康检查 URL、主页 URL、状态页 URL 等元数据。
POST /eureka/v2/apps/{applicationName}
请求体 (JSON):
{
"instance": {
"instanceId": "192.168.1.10:user-service:8080",
"hostName": "192.168.1.10",
"app": "USER-SERVICE",
"ipAddr": "192.168.1.10",
"status": "UP",
"port": { "$": 8080, "@enabled": "true" },
"healthCheckUrl": "http://192.168.1.10:8080/actuator/health",
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
}
}
}第二步:心跳续约(Renew)
注册成功后,服务提供者每隔 30 秒(默认)向 Eureka Server 发送一次心跳,表示自己仍然存活。如果 Eureka Server 在 90 秒内没有收到心跳,就会将此服务实例从注册表中剔除。
PUT /eureka/v2/apps/{applicationName}/{instanceId}第三步:获取注册表(Fetch Registry)
服务消费者启动时,会从 Eureka Server 全量拉取一次注册表信息。之后每 30 秒发送一次增量拉取请求,只获取变更的实例信息,减少网络开销。
全量拉取:GET /eureka/v2/apps
增量拉取:GET /eureka/v2/apps/delta第四步:服务调用
服务消费者从本地缓存的注册表中找到目标服务名对应的实例列表,通过 Ribbon/LoadBalancer 进行客户端负载均衡,选择一个实例发起 HTTP 调用。这一过程完全不经过 Eureka Server。
第五步:服务注销(Cancel)
服务提供者正常关闭时,会向 Eureka Server 发送注销请求,主动将自己从注册表中移除。如果服务异常宕机,Eureka Server 会在 90 秒没有收到心跳后自动剔除。
DELETE /eureka/v2/apps/{applicationName}/{instanceId}3.3 三级缓存机制
Eureka Server 为了提升响应性能,内部设计了一套三级缓存体系:
请求到达 Eureka Server
│
▼
┌──────────────────┐
│ ReadOnlyCache │ ← 只读缓存,默认每 30s 从 ReadWriteCache 同步
│ (ConcurrentHashMap) │ 响应时间:微秒级
└────────┬─────────┘
│ 未命中
▼
┌──────────────────┐
│ ReadWriteCache │ ← 读写缓存,默认每 180s 过期
│ (Guava Cache) │ 响应时间:毫秒级
└────────┬─────────┘
│ 未命中
▼
┌──────────────────┐
│ Registry │ ← 真实注册表(双层 ConcurrentHashMap)
│ (内存数据结构) │ 响应时间:纳秒级
└──────────────────┘| 缓存层 | 数据结构 | 刷新周期 | 说明 |
|---|---|---|---|
| ReadOnlyCache | ConcurrentHashMap | 每 30s 从 ReadWriteCache 同步 | 主要提供对外查询,性能最高 |
| ReadWriteCache | Guava Cache | 每 180s 过期 | 写入时更新,读取时查 Registry |
| Registry | ConcurrentHashMap | 实时 | 真实的数据存储层 |
四、CAP 理论中的 Eureka
4.1 CAP 理论回顾
CAP 理论由 Eric Brewer 提出,是分布式系统设计的基石。它指出:在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)三者不可兼得,最多只能同时满足其中两个。
| 属性 | 含义 |
|---|---|
| C(Consistency)一致性 | 所有节点在同一时刻看到的数据完全相同 |
| A(Availability)可用性 | 每个请求都能在有限时间内收到非错误的响应(不保证数据是最新的) |
| P(Partition Tolerance)分区容错性 | 系统在出现网络分区(节点间通信中断)时仍能继续正常工作 |
4.2 Eureka 是 AP 系统
Eureka 在设计上选择了 AP(可用性 + 分区容错性),而非 CP(一致性 + 分区容错性)。这意味着在网络分区发生时,Eureka 优先保证系统的可用性,允许各节点之间存在短暂的数据不一致。
CAP 取舍对比:
C (一致性)
/\
/ \
/ \
/ CP \ Zookeeper、Consul 在这边
/ 领域 \
/ \
A────────────P
\ /
\ AP / Eureka、Nacos 在这边
\ 领域 /
\ /
\ /
\/
Eureka 的选择:AP
- 优先保证可用性:即使部分节点不可达,剩余节点仍可提供服务
- 牺牲强一致性:各节点之间可能存在短暂的数据不一致
- 最终一致性:通过 Peer-to-Peer 复制,最终达到数据一致4.3 网络分区场景下的行为
假设 Eureka 集群有 3 个节点,由于网络故障,节点 A 与节点 B、C 之间通信中断:
网络分区前:
┌─────┐ ┌─────┐ ┌─────┐
│ A │◄───►│ B │◄───►│ C │
└─────┘ └─────┘ └─────┘
网络分区后:
┌─────┐ ┌─────┐ ┌─────┐
│ A │ ✗ │ B │◄───►│ C │
└─────┘ └─────┘ └─────┘
独立分区 正常通信的分区
Eureka 的行为(AP):
✅ 节点 A 仍然可以接受注册和查询请求
✅ 节点 B 和 C 也可以正常提供服务
❌ 节点 A 上的注册信息与 B、C 上的可能不一致(短暂不一致)
✅ 分区恢复后,通过 Peer-to-Peer 复制,最终达到一致
Zookeeper 的行为(CP):
❌ 节点 A 发现与半数以上节点失联,自动进入不可用状态
✅ 只有 B 和 C 组成的多数派可以提供服务
❌ 节点 A 上的服务完全不可用,即使自身是健康的4.4 为什么 Eureka 选择 AP?
在微服务架构中,注册中心的可用性远比一致性重要。原因如下:
服务调用的本质是"尽力而为":即使注册表中的数据不是最新的,调用方仍然可以通过重试、熔断等机制保证调用成功率。一个"过时的"实例列表远比"没有"实例列表要好。
网络分区时服务间仍需通信:如果因为网络分区导致注册中心不可用,整个微服务体系的调用链路都会断裂。Eureka 选择 AP,确保即使网络分区,各分区的服务仍可以正常注册和发现。
最终一致性足够:微服务实例的上下线变化频率不高(秒级到分钟级),短暂的不一致(几秒到几十秒)对业务影响很小。
自我保护机制体现 AP 思想:Eureka 宁愿保留可能已经不健康的实例,也不愿意因为误判而剔除健康实例。这与 AP 的设计理念一脉相承。
五、核心配置
5.1 Eureka Server 配置
yaml
# Eureka Server 单节点配置 (application.yml)
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
# 是否将自己注册到 Eureka Server(Server 端通常设为 false)
registerWithEureka: false
# 是否从 Eureka Server 拉取注册表(Server 端通常设为 false)
fetchRegistry: false
# Eureka Server 的地址
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
# 是否开启自我保护模式(默认 true,生产环境建议开启)
enableSelfPreservation: true
# 自我保护模式的心跳阈值因子(默认 0.85,即 85%)
renewalPercentThreshold: 0.85
# 剔除无效实例的定时任务间隔(默认 60000ms,即 60s)
evictionIntervalTimerInMs: 60000
# ReadOnlyCache 从 ReadWriteCache 同步的间隔(默认 30000ms)
responseCacheUpdateIntervalMs: 30000
# ReadWriteCache 过期时间(默认 180000ms)
responseCacheAutoExpirationInSeconds: 1805.2 Eureka Client 配置(服务提供者)
yaml
# 服务提供者配置 (application.yml)
server:
port: 8081
spring:
application:
name: user-service # 服务名,注册到 Eureka 时的唯一标识
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
# 是否从 Eureka Server 拉取注册表(提供者可以不拉取)
fetchRegistry: true
instance:
# 心跳续约间隔(默认 30s)
leaseRenewalIntervalInSeconds: 30
# 心跳过期时间(默认 90s),超过此时间未收到心跳则剔除
leaseExpirationDurationInSeconds: 90
# 优先使用 IP 注册而非主机名
preferIpAddress: true
# 实例 ID 格式
instanceId: ${spring.application.name}:${server.port}5.3 Eureka Client 配置(服务消费者)
yaml
# 服务消费者配置 (application.yml)
server:
port: 8082
spring:
application:
name: order-service
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
# 消费者必须拉取注册表
fetchRegistry: true
# 注册表拉取间隔(默认 30s)
registryFetchIntervalSeconds: 30
# 是否将自己注册到 Eureka(消费者通常也需要注册,以便被监控)
registerWithEureka: true
instance:
leaseRenewalIntervalInSeconds: 30
leaseExpirationDurationInSeconds: 90
preferIpAddress: true5.4 Eureka Server 集群配置
Eureka Server 采用 Peer-to-Peer 对等架构,所有节点地位平等。配置集群时,每个节点需要将自己注册到其他节点上。
yaml
# Eureka Server 节点 1 (application-peer1.yml)
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: peer1
client:
# 集群模式下,需要注册到其他节点
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://peer2:8762/eureka/,http://peer3:8763/eureka/
server:
enableSelfPreservation: trueyaml
# Eureka Server 节点 2 (application-peer2.yml)
server:
port: 8762
spring:
application:
name: eureka-server
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer3:8763/eureka/yaml
# Eureka Server 节点 3 (application-peer3.yml)
server:
port: 8763
spring:
application:
name: eureka-server
eureka:
instance:
hostname: peer3
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/服务提供者/消费者连接集群的配置:
yaml
# 客户端配置 — 连接多个 Eureka Server 节点
eureka:
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/,http://peer3:8763/eureka/5.5 关键配置项速查表
| 配置项 | 默认值 | 说明 |
|---|---|---|
eureka.server.enableSelfPreservation | true | 是否开启自我保护模式 |
eureka.server.renewalPercentThreshold | 0.85 | 自我保护触发阈值(心跳比例低于此值触发) |
eureka.server.evictionIntervalTimerInMs | 60000 | 剔除任务的执行间隔(毫秒) |
eureka.server.responseCacheUpdateIntervalMs | 30000 | ReadOnlyCache 同步间隔(毫秒) |
eureka.instance.leaseRenewalIntervalInSeconds | 30 | 客户端心跳续约间隔(秒) |
eureka.instance.leaseExpirationDurationInSeconds | 90 | 服务端剔除过期实例的等待时间(秒) |
eureka.client.registryFetchIntervalSeconds | 30 | 客户端拉取注册表的间隔(秒) |
eureka.client.registerWithEureka | true | 客户端是否注册到 Eureka |
eureka.client.fetchRegistry | true | 客户端是否拉取注册表 |
六、自我保护机制详解
6.1 为什么需要自我保护?
自我保护机制是 Eureka 最核心的亮点功能之一。它的设计初衷是:在网络出现短暂故障时,避免因为误判而大量剔除健康的服务实例。
场景:没有自我保护机制时
正常状态:
Eureka Server 管理 100 个服务实例
每分钟收到 100 个实例的心跳请求
网络出现短暂故障(如交换机重启、网络抖动):
1 分钟内,只有 10 个实例的心跳成功到达
其他 90 个实例的心跳因为网络原因丢失
如果没有自我保护:
Eureka 认为 90 个实例已经宕机
立即将这 90 个实例从注册表中剔除
结果:90% 的服务调用失败!
但实际上:这 90 个实例都是健康的,只是网络不通!
网络恢复后,这 90 个实例需要重新注册,造成大量流量冲击6.2 触发条件
自我保护模式的触发条件是:在 15 分钟内,实际收到的心跳数低于期望心跳数的 85%。
触发条件计算:
期望心跳数 = 注册的实例总数 × 每分钟应收到的心跳数(2 次/分钟) × 15 分钟
= 100 × 2 × 15
= 3000 次
实际心跳数 = 在 15 分钟内实际收到的心跳总数
触发阈值 = 期望心跳数 × 85% = 3000 × 0.85 = 2550 次
如果实际心跳数 < 2550 次 → 触发自我保护模式6.3 自我保护模式下的行为
当自我保护模式触发后,Eureka Server 会:
- 不再剔除任何服务实例(即使某个实例已经超过 90 秒没有发送心跳)
- 保留所有已注册的实例信息,包括可能已经宕机的实例
- 在 Dashboard 上显示红色警告:
RENEWALS ARE LESSER THAN THE THRESHOLD - 继续接受新的注册请求,新的服务实例可以正常注册
自我保护模式下的 Dashboard 提示:
┌─────────────────────────────────────────────────────┐
│ EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING │
│ INSTANCES ARE UP WHEN THEY'RE NOT. │
│ RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE │
│ INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE. │
└─────────────────────────────────────────────────────┘6.4 自我保护模式的退出
当网络恢复,心跳数重新达到阈值后,Eureka Server 会自动退出自我保护模式,恢复正常的心跳检测和实例剔除逻辑。
6.5 何时关闭自我保护?
自我保护模式在生产环境中建议保持开启,但在以下场景可以考虑关闭:
| 场景 | 建议 |
|---|---|
| 开发/测试环境 | 关闭,便于快速发现实例异常 |
| 网络稳定的内网环境 | 可以关闭,但建议保留 |
| 生产环境 | 强烈建议开启,防止网络抖动导致大面积服务不可用 |
| 小规模集群(< 10 个实例) | 建议关闭,因为少量实例宕机就可能触发 85% 阈值 |
yaml
# 关闭自我保护模式
eureka:
server:
enableSelfPreservation: false
# 同时可以调整剔除间隔,加快异常实例剔除
evictionIntervalTimerInMs: 100006.6 自我保护机制的局限性
自我保护机制虽然保护了服务可用性,但也带来了一些问题:
- 可能保留已宕机的实例:如果确实有实例宕机,自我保护模式下它不会被剔除,调用方可能拿到一个已宕机的实例地址
- 依赖调用方的容错机制:需要配合 Ribbon 的重试、Hystrix/Sentinel 的熔断来保证调用成功率
- 不适合所有场景:对于一致性要求极高的系统,自我保护机制可能带来数据不一致
七、Eureka Server 集群
7.1 Peer-to-Peer 对等架构
Eureka 集群采用的是 Peer-to-Peer(对等)架构,而非传统的 Master-Slave(主从)架构。每个 Eureka Server 节点地位完全平等,它们之间互相注册、互相复制数据。
Eureka 集群架构(Peer-to-Peer):
┌──────────────────────┐
│ │
▼ │
┌───────────────┐ ┌───────────────┐
│ Eureka │◄─────►│ Eureka │
│ Server A │ │ Server B │
│ (Peer 1) │ │ (Peer 2) │
└───────┬───────┘ └───────┬───────┘
│ │
│ ┌───────────────┐ │
└───►│ Eureka │◄─┘
│ Server C │
│ (Peer 3) │
└───────────────┘
特点:
1. 没有主节点,所有节点平等
2. 每个节点都保存完整的注册表
3. 任意节点挂掉不影响集群整体可用性
4. 数据通过 Peer-to-Peer 复制实现最终一致性对比 Master-Slave 架构:
| 对比维度 | Peer-to-Peer(Eureka) | Master-Slave(Zookeeper) |
|---|---|---|
| 节点角色 | 所有节点平等 | 有 Leader 和 Follower 之分 |
| 写操作 | 任意节点都可接受写操作 | 写操作必须经过 Leader |
| 数据一致性 | 最终一致性 | 强一致性(ZAB 协议) |
| 可用性 | 任意节点挂掉不影响服务 | Leader 挂掉需要重新选举(短暂不可用) |
| 扩展性 | 简单,加节点即可 | 新增节点需要同步数据 |
7.2 数据同步机制
Eureka 集群的数据同步通过以下机制实现:
服务提供者注册到 Server A 后的数据同步流程:
服务提供者
│
│ 1. 注册请求
▼
┌───────────────┐
│ Eureka │
│ Server A │────── 2. 写入本地注册表
│ │
│ │────── 3. 复制到 Server B(POST /eureka/v2/apps)
│ │
│ │────── 4. 复制到 Server C(POST /eureka/v2/apps)
└───────────────┘
如果 Server B 不可达(网络分区):
- Server A 会将复制请求放入重试队列
- 待 Server B 恢复后,重新尝试复制
- 期间 Server A 和 Server C 的数据是一致的
- Server B 恢复后通过心跳同步机制补齐数据同步规则:
- 写操作复制:任何一个 Eureka Server 收到注册、续约、注销请求后,都会将操作复制到其他所有 Peer 节点
- 复制不保证成功:如果某个 Peer 不可达,不会阻塞当前操作,而是放入重试队列
- 心跳不复制:服务提供者到 Server A 的心跳不会被复制到 Server B 和 Server C,各节点独立维护心跳状态
- 最终一致性:通过复制和重试机制,最终所有节点数据一致
7.3 集群部署最佳实践
推荐的三节点集群部署方案:
┌──────────────────┐
│ DNS / Nginx │
│ 负载均衡入口 │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Eureka │ │ Eureka │ │ Eureka │
│ Peer 1 │ │ Peer 2 │ │ Peer 3 │
│ 机房 A │ │ 机房 A │ │ 机房 B │
└──────────┘ └──────────┘ └──────────┘
建议:
1. 至少 3 个节点,保证高可用
2. 跨机房部署,防止单机房故障
3. 客户端配置所有节点的地址,不要只配一个
4. 开发环境可以用 1 个节点,测试环境 2 个,生产环境 3 个以上八、与 Spring Cloud 集成
8.1 依赖配置
Eureka Server 依赖:
xml
<!-- pom.xml — Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>Eureka Client 依赖(服务提供者/消费者):
xml
<!-- pom.xml — Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>8.2 Eureka Server 启动类
java
package com.example.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* Eureka Server 启动类
*
* @EnableEurekaServer 注解表示这是一个 Eureka 注册中心服务端
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}8.3 RestTemplate + @LoadBalanced 实现服务调用
java
package com.example.order.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate 配置类
*
* @LoadBalanced 注解为 RestTemplate 注入负载均衡拦截器
* 实现通过服务名调用 + 客户端负载均衡
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 开启负载均衡能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}java
package com.example.order.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 订单服务 — 通过 RestTemplate 调用用户服务
*/
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
/**
* 通过服务名调用,无需写 IP:Port
* Eureka + Ribbon 会自动解析服务名 → 选择实例 → 发起调用
*/
@GetMapping("/order/{id}")
public String getOrder(@PathVariable Long id) {
// "user-service" 是 Eureka 注册中心中的服务名
String userInfo = restTemplate.getForObject(
"http://user-service/user/" + id, String.class);
return "订单信息 + 用户信息:" + userInfo;
}
}8.4 Feign 声明式调用
java
package com.example.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* Feign 声明式服务调用接口
*
* @FeignClient 注解指定服务名,Feign 会自动:
* 1. 从 Eureka 获取 user-service 的实例列表
* 2. 通过 Ribbon/LoadBalancer 进行负载均衡
* 3. 发起 HTTP 调用
*/
@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/user/{id}")
String getUserById(@PathVariable("id") Long id);
}java
package com.example.order.controller;
import com.example.order.feign.UserServiceClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* 订单服务 — 通过 Feign 调用用户服务
*/
@RestController
public class FeignOrderController {
@Autowired
private UserServiceClient userServiceClient;
@GetMapping("/feign/order/{id}")
public String getOrder(@PathVariable Long id) {
// 像调用本地方法一样调用远程服务
String userInfo = userServiceClient.getUserById(id);
return "Feign 订单信息 + 用户信息:" + userInfo;
}
}启动类需添加 @EnableFeignClients:
java
package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients // 开启 Feign 支持
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}8.5 调用链路总结
┌─────────────────────────────────────────────────────────────────┐
│ 服务调用完整链路 │
│ │
│ OrderService (消费者) │
│ │ │
│ │ @FeignClient(name = "user-service") │
│ │ 或 RestTemplate + http://user-service/... │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Ribbon / │ 1. 从本地缓存获取 user-service 实例列表 │
│ │ LoadBalancer │ 2. 通过负载均衡策略选择一个实例 │
│ └────────┬────────┘ 3. 将服务名替换为实际的 IP:Port │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ HTTP Client │ ───► │ UserService │ │
│ │ (发起调用) │ │ 192.168.1.10:8081│ │
│ └─────────────────┘ └──────────────────┘ │
│ │
│ 本地缓存由 Eureka Client 定期(每 30s)从 Eureka Server 更新 │
└─────────────────────────────────────────────────────────────────┘九、Eureka vs Nacos vs Zookeeper vs Consul
9.1 全方位对比
| 对比维度 | Eureka | Nacos | Zookeeper | Consul |
|---|---|---|---|---|
| CAP 模型 | AP(可用性 + 分区容错) | AP/CP 可切换(默认 AP) | CP(一致性 + 分区容错) | CP(一致性 + 分区容错) |
| 一致性协议 | Peer-to-Peer 复制(最终一致性) | 自研 Distro 协议(AP)/ Raft 协议(CP) | ZAB 协议(类 Paxos) | Raft 协议 |
| 健康检查 | 客户端心跳(HTTP) | TCP/HTTP/MySQL 多种方式 | 基于 Session + 心跳(临时节点) | TCP/HTTP/gRPC + Script |
| 服务注销 | 心跳超时 90s 自动剔除 | 心跳超时自动剔除 | Session 超时自动删除临时节点 | 心跳超时自动剔除 |
| 自我保护 | 支持(核心特性) | 支持(可配置) | 不支持 | 不支持 |
| 管理控制台 | 有(功能简单,仅做展示) | 有(功能强大,支持服务管理) | 无原生控制台,需第三方工具 | 有(功能丰富) |
| 配置中心 | 不支持(需配合 Spring Cloud Config) | 原生支持(注册中心 + 配置中心一体) | 可实现(但非原生设计) | 原生支持(KV 存储) |
| 多数据中心 | 支持(通过 Region/Zone) | 支持(命名空间) | 不支持 | 原生支持(多 DC) |
| Spring Cloud 集成 | spring-cloud-starter-netflix-eureka | spring-cloud-starter-alibaba-nacos | spring-cloud-starter-zookeeper | spring-cloud-starter-consul |
| 社区活跃度 | 已停止维护(Eureka 2.0 终止) | 活跃(阿里巴巴维护) | 活跃(Apache 顶级项目) | 活跃(HashiCorp 维护) |
| 学习成本 | 低 | 中 | 高 | 中 |
| 生产成熟度 | 高(Netflix 大规模验证) | 高(阿里双十一验证) | 高(大数据领域广泛使用) | 中高 |
| 适用场景 | 纯 Spring Cloud 微服务 | Spring Cloud Alibaba 生态 | 大数据生态(Kafka、Dubbo) | 多语言异构系统 |
9.2 架构模式对比
Eureka (Peer-to-Peer 对等架构):
┌──────┐ ┌──────┐ ┌──────┐
│ Node │◄───►│ Node │◄───►│ Node │ 所有节点平等,最终一致性
└──────┘ └──────┘ └──────┘
Zookeeper (Leader-Follower 主从架构):
┌──────────┐
│ Leader │ ← 负责写操作
└────┬─────┘
┌────────┼────────┐
▼ ▼ ▼
┌────────┐┌────────┐┌────────┐
│Follower││Follower││Follower│ ← 负责读操作,强一致性
└────────┘└────────┘└────────┘
Consul (Raft 共识架构):
┌──────────┐
│ Leader │ ← 负责所有写操作
└────┬─────┘
┌────────┼────────┐
▼ ▼ ▼
┌────────┐┌────────┐┌────────┐
│ Server ││ Server ││ Server │ ← 强一致性(Raft)
└────────┘└────────┘└────────┘
│ │ │
▼ ▼ ▼
┌────────┐┌────────┐┌────────┐
│ Client ││ Client ││ Client │ ← 每个节点部署 Consul Agent
└────────┘└────────┘└────────┘9.3 如何选择?
| 场景 | 推荐 |
|---|---|
| 纯 Spring Cloud Alibaba 技术栈 | Nacos(注册中心 + 配置中心一体) |
| Netflix OSS 技术栈,或维护老项目 | Eureka(生态成熟,文档丰富) |
| 大数据生态(Kafka、Hadoop、HBase) | Zookeeper(事实标准) |
| 多语言异构系统、Kubernetes 外部 | Consul(多语言 SDK 支持好) |
| 新项目,2024 年以后 | Nacos(活跃维护,功能全面) |
十、为什么 Eureka 被 Nacos 取代?
10.1 Eureka 的衰落时间线
2018 年 6 月:Netflix 宣布 Eureka 2.0 停止开发
理由:Eureka 2.0 的架构过于复杂,投入产出比不高
2019 年 7 月:Nacos 1.0 GA 发布
阿里巴巴开源,天然支持 Spring Cloud Alibaba 生态
2020 年 12 月:Spring Cloud 官方宣布 Netflix 系列组件进入维护模式
包括 Eureka、Ribbon、Hystrix、Zuul 等
2021 年 ~ 至今:Nacos 成为 Spring Cloud 生态中注册中心的首选
阿里双十一验证,大规模生产环境考验10.2 Eureka 被取代的核心原因
| 原因 | 详细说明 |
|---|---|
| Eureka 2.0 终止开发 | Netflix 不再投入资源开发 Eureka 2.0,社区失去信心 |
| Netflix 组件集体进入维护模式 | Spring Cloud 官方宣布 Netflix 系列组件进入维护模式,不再添加新功能 |
| Nacos 功能更全面 | Nacos 同时提供注册中心 + 配置中心,Eureka 只能做注册中心,还需配合 Spring Cloud Config 做配置管理 |
| Nacos 控制台更强大 | Nacos 的 Web 控制台支持在线管理服务实例、编辑配置、查看服务拓扑,Eureka 的 Dashboard 只能做基本展示 |
| 中国开源生态的崛起 | Nacos 中文文档、中文社区活跃,对国内开发者更友好 |
| Nacos 支持多协议 | Nacos 不仅支持 HTTP 健康检查,还支持 TCP、MySQL 等多种方式,比 Eureka 的心跳机制更灵活 |
| Nacos 支持 AP/CP 切换 | Nacos 可以根据场景选择 AP 或 CP 模型,比 Eureka 只支持 AP 更加灵活 |
| 容器化适配更好 | Nacos 对 Kubernetes、Docker 等容器化部署的支持更完善 |
10.3 Eureka 是否还有学习的价值?
尽管 Eureka 在生产环境中逐渐被 Nacos 取代,但其学习价值依然很高:
- 理解 CAP 理论的最佳案例:Eureka 的自我保护机制和 AP 设计是分布式系统设计的经典教材级案例
- 维护老项目的需要:大量存量的 Spring Cloud 项目仍在使用 Eureka
- 面试高频考点:Eureka 的核心概念(自我保护、Peer-to-Peer 复制、CAP 取舍)是面试必问内容
- 设计思想可迁移:Eureka 的设计理念(服务注册、健康检查、缓存机制)是所有注册中心共通的
十一、面试要点
Q1:Eureka 的自我保护机制是什么?触发条件是什么?
回答要点:
- 自我保护机制是 Eureka 为了避免因网络抖动导致大量误剔除服务实例而设计的保护功能
- 触发条件:15 分钟内,实际收到的心跳数低于期望心跳数的 85%
- 触发后行为:不再剔除任何服务实例(即使心跳超时),保留所有注册信息
- 设计理念:宁可保留可能不健康的实例,也不误剔除健康的实例(AP 思想的体现)
- 退出条件:心跳数恢复正常后自动退出
- 生产环境建议开启,开发/测试环境可以关闭
Q2:Eureka 是 AP 还是 CP?为什么?
回答要点:
- Eureka 是 AP 系统(可用性 + 分区容错性)
- 在网络分区发生时,Eureka 优先保证可用性,各节点可以独立提供服务
- 牺牲强一致性,采用最终一致性模型(Peer-to-Peer 复制)
- 与 Zookeeper(CP)的对比:ZK 在网络分区时,少数派节点会不可用;Eureka 所有节点都可以继续服务
- 自我保护机制也是 AP 思想的具体体现
Q3:Eureka 集群的对等复制机制是怎样的?
回答要点:
- Eureka 采用 Peer-to-Peer 对等架构,所有节点地位平等,没有主从之分
- 写操作复制:任意节点收到注册/续约/注销请求后,会复制到其他所有 Peer 节点
- 心跳不复制:各节点独立维护心跳状态
- 复制失败处理:放入重试队列,等待 Peer 恢复后重试
- 最终一致性:通过复制和重试机制,最终所有节点数据一致
- 与 Zookeeper 的 Leader 选举机制对比:Eureka 没有 Leader 选举,因此没有选举期间的服务不可用问题
Q4:Eureka 的心跳机制是怎样的?
回答要点:
- 服务提供者每隔 30 秒(
leaseRenewalIntervalInSeconds)向 Eureka Server 发送一次心跳 - 如果 Eureka Server 在 90 秒(
leaseExpirationDurationInSeconds)内没有收到心跳,则将该实例剔除 - 剔除操作由定时任务执行,默认每 60 秒(
evictionIntervalTimerInMs)执行一次 - 心跳是 HTTP PUT 请求:
PUT /eureka/v2/apps/{appName}/{instanceId} - 当自我保护模式开启时,即使心跳超时也不会剔除实例
Q5:Eureka 和 Zookeeper 的区别?
回答要点:
| 对比维度 | Eureka | Zookeeper |
|---|---|---|
| CAP 模型 | AP | CP |
| 一致性协议 | Peer-to-Peer 复制 | ZAB 协议 |
| 架构 | 对等架构 | Leader-Follower |
| 自我保护 | 有 | 无 |
| 网络分区时的行为 | 所有节点可用 | 少数派节点不可用 |
| 适用场景 | 微服务注册发现 | 分布式协调、大数据 |
- 核心差异:Eureka 取可用性舍一致性,Zookeeper 取一致性舍可用性
- 在微服务场景下,注册中心的可用性比一致性更重要,这是 Eureka 设计的出发点
Q6:Eureka 的三级缓存机制是什么?
回答要点:
- Eureka Server 内部有三层缓存:ReadOnlyCache、ReadWriteCache、Registry
- ReadOnlyCache:ConcurrentHashMap,每 30s 从 ReadWriteCache 同步,响应最快(微秒级)
- ReadWriteCache:Guava Cache,每 180s 过期,写入时更新
- Registry:真实的注册表数据,双层 ConcurrentHashMap,实时更新
- 设计目的:减少并发读写冲突,提升查询性能
- 代价:数据存在短暂延迟(最多 30s),但 Eureka 是 AP 系统,可以接受
附录:Eureka Dashboard 说明
Eureka Server 默认提供了一个 Web 控制台页面,访问 http://localhost:8761 即可看到:
┌──────────────────────────────────────────────────────────────────┐
│ System Status │
│ ┌──────────────────────────────────────────────────────────────┐│
│ │ Environment: test Data center: default ││
│ │ Current time: 2024-01-01T10:00:00 +0800 ││
│ │ Uptime: 7 days Lease expiration enabled: true ││
│ │ Renews threshold: 15 Renews (last min): 18 ││
│ │ ││
│ │ ⚠ EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ││
│ │ ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN ││
│ │ THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED. ││
│ └──────────────────────────────────────────────────────────────┘│
│ │
│ DS Replicas (Peer 节点) │
│ ┌──────────────────────────────────────────────────────────────┐│
│ │ peer2 UP (http://peer2:8762/eureka/) ││
│ │ peer3 UP (http://peer3:8763/eureka/) ││
│ └──────────────────────────────────────────────────────────────┘│
│ │
│ Instances currently registered with Eureka │
│ ┌──────────────────────────────────────────────────────────────┐│
│ │ Application AMIs Status Availability Zones ││
│ │ ORDER-SERVICE n/a (1) UP (1) defaultZone ││
│ │ USER-SERVICE n/a (3) UP (3) defaultZone ││
│ └──────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────┘Dashboard 关键指标说明:
| 指标 | 说明 |
|---|---|
| Renews threshold | 自我保护阈值(期望心跳数) |
| Renews (last min) | 上一分钟实际收到的心跳数 |
| DS Replicas | 集群中其他 Eureka Server 节点的状态 |
| Instances registered | 当前注册的服务实例列表 |
总结:Eureka 作为 Spring Cloud 生态中最早的服务注册中心,虽然已经进入维护模式,但其设计思想——AP 模型、自我保护机制、Peer-to-Peer 对等架构——仍然是分布式系统设计的经典教科书。理解 Eureka 不仅是为了维护老项目,更是为了深入理解微服务架构中注册中心的设计哲学。
