Skip to content

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. 无需硬编码任何 IP

Eureka 的核心价值:

价值说明
服务注册服务启动时自动注册到 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)
│  (内存数据结构)    │   响应时间:纳秒级
└──────────────────┘
缓存层数据结构刷新周期说明
ReadOnlyCacheConcurrentHashMap每 30s 从 ReadWriteCache 同步主要提供对外查询,性能最高
ReadWriteCacheGuava Cache每 180s 过期写入时更新,读取时查 Registry
RegistryConcurrentHashMap实时真实的数据存储层

四、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?

  在微服务架构中,注册中心的可用性远比一致性重要。原因如下:

  1. 服务调用的本质是"尽力而为":即使注册表中的数据不是最新的,调用方仍然可以通过重试、熔断等机制保证调用成功率。一个"过时的"实例列表远比"没有"实例列表要好。

  2. 网络分区时服务间仍需通信:如果因为网络分区导致注册中心不可用,整个微服务体系的调用链路都会断裂。Eureka 选择 AP,确保即使网络分区,各分区的服务仍可以正常注册和发现。

  3. 最终一致性足够:微服务实例的上下线变化频率不高(秒级到分钟级),短暂的不一致(几秒到几十秒)对业务影响很小。

  4. 自我保护机制体现 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: 180

5.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: true

5.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: true
yaml
# 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.enableSelfPreservationtrue是否开启自我保护模式
eureka.server.renewalPercentThreshold0.85自我保护触发阈值(心跳比例低于此值触发)
eureka.server.evictionIntervalTimerInMs60000剔除任务的执行间隔(毫秒)
eureka.server.responseCacheUpdateIntervalMs30000ReadOnlyCache 同步间隔(毫秒)
eureka.instance.leaseRenewalIntervalInSeconds30客户端心跳续约间隔(秒)
eureka.instance.leaseExpirationDurationInSeconds90服务端剔除过期实例的等待时间(秒)
eureka.client.registryFetchIntervalSeconds30客户端拉取注册表的间隔(秒)
eureka.client.registerWithEurekatrue客户端是否注册到 Eureka
eureka.client.fetchRegistrytrue客户端是否拉取注册表

六、自我保护机制详解

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 会:

  1. 不再剔除任何服务实例(即使某个实例已经超过 90 秒没有发送心跳)
  2. 保留所有已注册的实例信息,包括可能已经宕机的实例
  3. 在 Dashboard 上显示红色警告RENEWALS ARE LESSER THAN THE THRESHOLD
  4. 继续接受新的注册请求,新的服务实例可以正常注册
自我保护模式下的 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: 10000

6.6 自我保护机制的局限性

  自我保护机制虽然保护了服务可用性,但也带来了一些问题:

  1. 可能保留已宕机的实例:如果确实有实例宕机,自我保护模式下它不会被剔除,调用方可能拿到一个已宕机的实例地址
  2. 依赖调用方的容错机制:需要配合 Ribbon 的重试、Hystrix/Sentinel 的熔断来保证调用成功率
  3. 不适合所有场景:对于一致性要求极高的系统,自我保护机制可能带来数据不一致

七、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 恢复后通过心跳同步机制补齐数据

同步规则:

  1. 写操作复制:任何一个 Eureka Server 收到注册、续约、注销请求后,都会将操作复制到其他所有 Peer 节点
  2. 复制不保证成功:如果某个 Peer 不可达,不会阻塞当前操作,而是放入重试队列
  3. 心跳不复制:服务提供者到 Server A 的心跳不会被复制到 Server B 和 Server C,各节点独立维护心跳状态
  4. 最终一致性:通过复制和重试机制,最终所有节点数据一致

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 全方位对比

对比维度EurekaNacosZookeeperConsul
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-eurekaspring-cloud-starter-alibaba-nacosspring-cloud-starter-zookeeperspring-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 取代,但其学习价值依然很高:

  1. 理解 CAP 理论的最佳案例:Eureka 的自我保护机制和 AP 设计是分布式系统设计的经典教材级案例
  2. 维护老项目的需要:大量存量的 Spring Cloud 项目仍在使用 Eureka
  3. 面试高频考点:Eureka 的核心概念(自我保护、Peer-to-Peer 复制、CAP 取舍)是面试必问内容
  4. 设计思想可迁移: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 的区别?

回答要点:

对比维度EurekaZookeeper
CAP 模型APCP
一致性协议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 不仅是为了维护老项目,更是为了深入理解微服务架构中注册中心的设计哲学。