Skip to content

Eureka 服务注册与发现

Eureka 是 Spring Cloud Netflix 体系里的注册中心。它解决的核心问题是:微服务实例地址会变化,调用方不能把 IP 和端口写死

单体应用里,订单模块调用库存模块只是进程内方法调用。拆成微服务后,订单服务、库存服务、资产服务、字典服务可能分别部署多个实例,而且实例会扩容、缩容、重启、下线。如果调用方还写死 http://10.1.2.3:8080,只要实例迁移或故障,调用就会失败。

Eureka 的职责就是维护一张“服务名 -> 实例列表”的注册表,让服务提供者启动后把自己注册上来,让服务消费者按服务名发现可用实例,再交给 Ribbon 或 Spring Cloud LoadBalancer 选择一个实例调用。

阅读入口:跟着一次实例生命周期走

按“注册 → 续约 → 拉取缓存 → 选择实例 → 剔除”阅读。先理解 Server 和 Client 的边界,再看自我保护与高可用,最后用服务发现排查验证本地缓存与服务端注册表。

核心原理:租约、缓存与最终一致

Eureka 不把每次业务请求转发到 Server,而是采用“客户端注册 + 定时续约 + 客户端拉取注册表”的控制面模型。服务端用租约判断实例是否仍然活跃,消费者把注册表缓存在本地,因此注册中心短暂不可用时,已有调用仍可能使用旧快照;代价是实例剔除存在传播延迟。

mermaid
sequenceDiagram
    participant P as 提供方 EurekaClient
    participant E as EurekaServer
    participant C as 消费方 EurekaClient
    P->>E: POST 注册实例与租约
    loop 每30秒左右
        P->>E: PUT heartbeat 续约
    end
    C->>E: GET 注册表(定期拉取)
    E-->>C: 返回实例快照并写入本地缓存
    E->>E: 租约过期后剔除实例
    C->>C: LoadBalancer 从本地快照选择实例

这里的关键原理是“可用性优先的最终一致”:心跳、剔除和缓存刷新不是同一时刻完成,排障时必须同时查看 Server 注册表和消费者本地实例快照。

学习目标

学完本页你要能回答:

  1. 注册中心到底解决什么问题。
  2. Eureka Server 和 Eureka Client 分别做什么。
  3. 服务注册、续约、拉取注册表、剔除实例的完整流程。
  4. 消费者为什么会有本地注册表缓存。
  5. Eureka 为什么偏 AP,不追求强一致。
  6. 自我保护机制为什么存在,不理解会造成什么误判。
  7. Eureka 和 Nacos、Consul、ZooKeeper 的区别。
  8. 注册中心挂了为什么短时间还能调用。
  9. 注册中心有实例但 Feign 仍报无实例怎么排查。
  10. 老项目维护 Eureka,新项目迁移 Nacos 时要注意什么。

为什么需要注册中心

先看没有注册中心时会发生什么。

mermaid
flowchart TD
    A["订单服务"] --> B["写死库存服务 IP:Port"]
    B --> C{"库存服务是否扩容/重启/迁移"}
    C -- "否" --> D["暂时能调用"]
    C -- "是" --> E["地址失效或实例不均衡"]
    E --> F["调用失败、发布困难、无法弹性扩容"]

写死地址的问题:

问题后果
实例重启后地址变化调用方配置要改
服务扩容调用方不知道新实例
服务缩容或宕机调用方仍可能打到旧地址
多实例负载均衡调用方要自己维护实例列表
多环境部署dev/test/prod 地址混乱
灰度和机房路由很难在调用方硬编码维护

有注册中心后,调用方只依赖服务名:

mermaid
flowchart TD
    A["库存服务实例 A"] --> B["注册到 Eureka"]
    C["库存服务实例 B"] --> B
    D["订单服务"] --> E["按 inventory-service 拉取实例列表"]
    E --> F["本地缓存注册表"]
    F --> G["负载均衡选择一个实例"]
    G --> H["发起 HTTP 调用"]

注册中心不是替你发 HTTP 请求,它只解决“有哪些实例可用”。真正的调用通常由 OpenFeign、RestTemplate、WebClient、HTTP Client 完成,实例选择由 Ribbon 或 Spring Cloud LoadBalancer 完成。

核心角色

角色作用类比
Eureka Server保存注册表,接收注册、续约、下线、查询服务通讯录中心
Service Provider服务提供者,把自己注册到 Eureka库存服务、资产服务
Service Consumer服务消费者,从 Eureka 获取实例列表订单服务、采集服务
Registry注册表,保存服务名和实例信息通讯录数据
Lease租约,表示实例在一段时间内有效定期签到

一个服务既可以是提供者,也可以是消费者。例如订单服务既对外提供订单接口,也可能调用库存服务、支付服务、优惠券服务。

Eureka 注册发现全过程

mermaid
flowchart TD
    A["服务实例启动"] --> B["读取服务名、IP、端口、元数据"]
    B --> C["向 Eureka Server 注册"]
    C --> D["Eureka 保存实例租约"]
    D --> E["实例定时发送心跳续约"]
    F["消费者启动"] --> G["从 Eureka 拉取注册表"]
    G --> H["缓存到本地"]
    H --> I["按服务名获取实例列表"]
    I --> J["负载均衡选择实例"]
    J --> K["发起远程调用"]
    E --> L{"超过剔除时间未续约"}
    L -- "否" --> D
    L -- "是" --> M["Eureka 剔除实例"]

这条链路里最重要的是四个动作:

动作谁发起目的
Register 注册服务实例告诉注册中心“我上线了”
Renew 续约服务实例定期告诉注册中心“我还活着”
Fetch 拉取消费者获取其他服务实例列表
Cancel 下线服务实例或 Server实例主动或被动从注册表移除

服务注册:实例启动时做什么

服务提供者启动后,会把自己的信息注册到 Eureka。

注册信息通常包括:

信息示例用途
appNameinventory-service服务名
instanceIdhost:inventory-service:8081实例唯一标识
ipAddr10.1.2.11调用地址
port8081调用端口
statusUP实例状态
metadataversion=v1, zone=shanghai灰度、机房、权重等扩展信息
leaseInfo续约间隔、过期时间判断实例是否存活

可以理解成:

text
inventory-service:
  - 10.1.2.11:8081, status=UP, zone=shanghai
  - 10.1.2.12:8081, status=UP, zone=shanghai
  - 10.1.3.21:8081, status=UP, zone=beijing

配置 Demo:服务提供者

yaml
spring:
  application:
    name: inventory-service

server:
  port: 8081

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
    metadata-map:
      zone: shanghai
      version: v1

prefer-ip-address: true 表示优先把 IP 注册上去。生产环境里要确认注册出去的 IP 是其他服务能访问到的地址,而不是容器内部不可达地址或 127.0.0.1

服务续约:为什么要心跳

服务注册上来不代表永远可用。实例可能宕机、网络断开、容器被杀、发布重启。Eureka 用续约心跳维护实例租约。

mermaid
flowchart TD
    A["实例注册成功"] --> B["每隔一段时间发送 Renew"]
    B --> C["Eureka 更新最后续约时间"]
    C --> D{"下一轮是否继续收到心跳"}
    D -- "是" --> C
    D -- "否" --> E["等待过期阈值"]
    E --> F{"是否超过剔除时间"}
    F -- "否" --> E
    F -- "是" --> G["从注册表剔除"]

常见参数:

参数含义
lease-renewal-interval-in-seconds客户端多久发一次心跳
lease-expiration-duration-in-secondsServer 多久没收到心跳认为过期

示例:

yaml
eureka:
  instance:
    lease-renewal-interval-in-seconds: 30
    lease-expiration-duration-in-seconds: 90

含义是:客户端大约每 30 秒续约一次;如果 90 秒没有续约,Server 才考虑剔除。

为什么不是 1 秒没心跳就剔除?因为网络抖动、GC 暂停、短暂 CPU 抢占都可能造成心跳延迟。过于敏感会导致健康实例被误删,调用方实例列表频繁变化。

服务发现:消费者为什么要本地缓存

消费者不会每次调用都实时访问 Eureka。它通常会周期性拉取注册表并缓存到本地。

mermaid
flowchart TD
    A["消费者启动"] --> B["从 Eureka 拉全量或增量注册表"]
    B --> C["保存到本地缓存"]
    D["业务发起调用"] --> E["按服务名查本地实例列表"]
    E --> F["负载均衡选择实例"]
    F --> G["HTTP 调用目标服务"]
    H["定时刷新注册表"] --> C

为什么要本地缓存?

原因说明
降低注册中心压力每次调用都查 Eureka,注册中心会被打爆
提高调用性能本地内存查实例比远程查询快
提高可用性Eureka 短暂不可用时,消费者还能用旧缓存继续调用
支持客户端负载均衡LoadBalancer 基于本地实例列表选实例

这也带来一个重要后果:服务上下线不是瞬间对所有消费者生效。注册表变化需要经过注册中心感知、同步、消费者刷新本地缓存、连接池释放旧连接等多个步骤。

调用链路:Eureka 和 Feign/LoadBalancer 的关系

mermaid
flowchart TD
    A["业务代码调用 Feign 接口"] --> B["Feign 动态代理"]
    B --> C["解析服务名 inventory-service"]
    C --> D["LoadBalancer 请求实例列表"]
    D --> E["Eureka Client 本地注册表缓存"]
    E --> F["返回多个 ServiceInstance"]
    F --> G["负载均衡算法选择一个实例"]
    G --> H["HTTP Client 调用 IP:Port"]

所以遇到 No instances available for inventory-service,不要只盯着 Eureka 控制台。你要确认:

  1. 调用方本地缓存里有没有这个服务。
  2. 服务名是否一致。
  3. namespace、profile、环境是否一致。
  4. 实例是否被健康、灰度、区域过滤掉。
  5. 客户端缓存是否及时刷新。
  6. HTTP Client 是否还复用旧连接。

Eureka Server 高可用

生产环境不能只部署一个 Eureka Server。常见做法是多个 Eureka Server 相互注册,彼此复制注册信息。

mermaid
flowchart TD
    A["Eureka Server A"] <--> B["Eureka Server B"]
    B <--> C["Eureka Server C"]
    C <--> A
    D["服务实例"] --> A
    D --> B
    E["消费者"] --> A
    E --> C

Eureka Server 集群不是强一致数据库集群。节点之间注册信息同步可能有延迟,所以短时间内不同 Server 看到的注册表可能不同。

配置示例:

yaml
spring:
  application:
    name: eureka-server

server:
  port: 8761

eureka:
  instance:
    hostname: eureka-a
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka-b:8762/eureka/,http://eureka-c:8763/eureka/

单机学习时可以设置:

yaml
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

但生产多节点通常要互相注册和拉取。

自我保护机制

Eureka 的自我保护机制是很多人面试会背、但不理解的点。

当 Eureka 在短时间内发现大量实例心跳丢失时,它不会立刻剔除所有这些实例。因为这可能不是服务真的都挂了,而是 Eureka Server 和服务实例之间出现网络分区。

mermaid
flowchart TD
    A["大量实例心跳丢失"] --> B{"可能是什么原因"}
    B --> C["实例真的大面积宕机"]
    B --> D["网络分区或 Eureka 自身网络异常"]
    D --> E["如果立刻剔除会误删健康实例"]
    E --> F["进入自我保护,保留注册表"]

自我保护的思想是:在不确定是服务挂了还是网络抖了时,宁愿保留可能过期的实例,也不要把整个注册表清空。

自我保护的收益和风险

维度说明
收益避免网络抖动时大面积误剔除,保证注册中心可用性
风险注册表可能保留已经宕机的实例,调用方可能打到坏实例

所以 Eureka 更偏 AP:优先保证注册中心可用,允许短时间注册信息不完全准确。

自我保护能不能关闭

开发或测试环境有时会关闭自我保护,方便快速看到实例下线:

yaml
eureka:
  server:
    enable-self-preservation: false

但生产环境不要轻易关闭。关闭后网络抖动时可能大面积剔除健康实例,引发服务发现雪崩。正确做法是结合健康检查、超时、重试、熔断和优雅下线治理调用失败,而不是指望注册表永远绝对准确。

Eureka 和 CAP

CAP 指分布式系统在网络分区发生时,无法同时完全满足一致性 Consistency 和可用性 Availability。

Eureka 的设计倾向:

CAP 维度Eureka 取舍
C 一致性不追求所有节点注册表瞬间一致
A 可用性注册中心尽量继续可用
P 分区容错网络分区下保留本地注册表和缓存

所以 Eureka 常说偏 AP。它接受注册表短时间不一致,换取服务发现能力尽量不停止。

这和 ZooKeeper 常见的 CP 思路不同。ZooKeeper 更强调一致性,网络分区或 Leader 选举期间可能牺牲部分可用性。服务注册发现到底选 AP 还是 CP,要看业务:微服务调用通常更怕整个注册中心不可用,所以 Eureka 选择了 AP。

Eureka 和 Nacos、Consul、ZooKeeper 对比

对比项EurekaNacosConsulZooKeeper
常见生态Spring Cloud NetflixSpring Cloud Alibaba多语言基础设施分布式协调
主要能力注册发现注册发现 + 配置中心注册发现 + KV + 健康检查协调、注册、配置存储
一致性倾向偏 AP支持临时/永久实例,能力更丰富偏 CP,健康检查强偏 CP
配置中心不提供内置KV 可承载可实现但非专用配置中心
健康检查客户端心跳为主心跳/服务端探测等健康检查成熟临时节点会话
新项目推荐度老项目维护常见Alibaba 生态常见多语言/基础设施常见协调场景常见

面试不要简单说“Eureka 过时了”。更成熟的回答是:

text
Eureka 是 Spring Cloud Netflix 老体系里的注册中心,老项目维护仍然常见。新项目如果使用 Spring Cloud Alibaba,常会选择 Nacos,因为它同时提供注册发现和配置中心,也有更活跃的生态。选型不是看新旧,而是看团队技术栈、配置中心需求、多语言支持、运维能力和迁移成本。

商业场景:医疗数据采集平台

假设平台拆成这些服务:

服务职责
gateway-service统一入口、鉴权、限流
collect-service医院数据采集任务
asset-service医疗资产管理
dict-service字典和编码映射
auth-service用户认证和权限

调用链路可能是:

mermaid
flowchart TD
    A["前端或定时任务"] --> B["gateway-service"]
    B --> C["collect-service"]
    C --> D["Feign 调用 dict-service"]
    C --> E["Feign 调用 asset-service"]
    D --> F["从 Eureka 发现字典服务实例"]
    E --> G["从 Eureka 发现资产服务实例"]

这里 Eureka 的价值:

  1. 采集服务不需要写死字典服务和资产服务 IP。
  2. 字典服务扩容后,采集服务能发现新实例。
  3. 字典服务某实例宕机后,后续注册表刷新会逐步剔除。
  4. 注册中心短暂不可用时,采集服务可以用本地缓存继续调用。

风险也要说清:

  1. 服务下线有传播延迟,调用方可能短时间打到旧实例。
  2. 注册表可能短时间不一致,必须配合超时、重试、熔断。
  3. 不能把 Eureka 当强一致健康判断系统。
  4. 灰度、同机房优先、权重路由要结合元数据和负载均衡扩展。

配置 Demo:Eureka Server

依赖示例:

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

启动类:

java
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

单机学习配置:

yaml
server:
  port: 8761

spring:
  application:
    name: eureka-server

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka/

访问 http://localhost:8761 可以看到 Eureka 控制台。

配置 Demo:Eureka Client

依赖示例:

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

服务提供者配置:

yaml
server:
  port: 8081

spring:
  application:
    name: inventory-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

启动类:

java
@SpringBootApplication
public class InventoryApplication {
    public static void main(String[] args) {
        SpringApplication.run(InventoryApplication.class, args);
    }
}

较新的 Spring Cloud 中,只要引入 Eureka Client 依赖并配置注册中心地址,通常不一定必须显式写 @EnableEurekaClient。老项目里看到这个注解也正常。

Feign 调用 Demo

库存服务提供接口:

java
@RestController
@RequestMapping("/inventory")
public class InventoryController {

    @GetMapping("/{skuId}")
    public InventoryDTO getInventory(@PathVariable Long skuId) {
        return new InventoryDTO(skuId, 100);
    }
}

订单服务 Feign Client:

java
@FeignClient(name = "inventory-service", path = "/inventory")
public interface InventoryClient {

    @GetMapping("/{skuId}")
    InventoryDTO getInventory(@PathVariable("skuId") Long skuId);
}

调用方:

java
@Service
public class OrderService {

    private final InventoryClient inventoryClient;

    public OrderService(InventoryClient inventoryClient) {
        this.inventoryClient = inventoryClient;
    }

    public void createOrder(Long skuId) {
        InventoryDTO inventory = inventoryClient.getInventory(skuId);
        if (inventory.getAvailable() <= 0) {
            throw new IllegalStateException("库存不足");
        }
    }
}

这段代码看起来像本地接口调用,但底层链路是:

text
OrderService -> Feign 代理 -> LoadBalancer -> Eureka 本地注册表 -> 选择实例 -> HTTP 请求

所以必须配置超时、错误处理和熔断,不能把 Feign 当普通本地方法。

生产排查:注册中心有实例但调用失败

现象一:Feign 报 No instances available

排查链路:

mermaid
flowchart TD
    A["No instances available"] --> B{"服务名是否写对"}
    B -- "否" --> C["修正 Feign name / spring.application.name"]
    B -- "是" --> D{"Eureka 控制台是否有实例"}
    D -- "否" --> E["查服务是否注册成功"]
    D -- "是" --> F{"调用方是否同环境"}
    F -- "否" --> G["查 defaultZone、profile、网络"]
    F -- "是" --> H{"本地缓存是否刷新"}
    H -- "否" --> I["等待刷新或重启验证"]
    H -- "是" --> J["查灰度、区域、健康过滤"]

常见原因:

原因说明
服务名不一致inventory-serviceinventory_service 写法不一致
注册到了另一个 Eurekadev/test/prod 注册中心混了
调用方缓存未刷新注册表拉取有周期
实例状态不是 UP被标记下线或健康异常
灰度过滤后为空version、zone、metadata 过滤导致无实例
网络不通调用方访问不到注册实例的 IP

现象二:服务下线后还被调用

这是注册发现系统的正常传播窗口。

mermaid
flowchart TD
    A["实例准备下线"] --> B["停止接收新流量或 Readiness=false"]
    B --> C["从注册中心摘除"]
    C --> D["等待消费者刷新本地注册表"]
    D --> E["等待旧连接和存量请求结束"]
    E --> F["关闭进程"]

如果直接 kill -9 或容器立刻退出,调用方还没刷新缓存,就可能继续调用旧实例。正确做法是优雅下线:先摘流,再等待,再停机。

现象三:Eureka 控制台实例很多但部分不可调用

要检查注册出去的地址是否可达。

容器或多网卡环境常见问题:

问题表现处理
注册成 localhost其他服务调用自己本机注册实际 IP
注册成容器内部 IP容器外服务不可达使用可路由地址或统一网络
多网卡选错跨网段不可达指定 prefer-ip-address 和网卡策略
端口映射错控制台有实例但连接失败确认实际暴露端口

常见坑

后果正确理解
把 Eureka 当强一致数据库误以为注册表瞬间准确Eureka 偏 AP,有缓存和同步延迟
只部署一个 Eureka Server注册中心单点故障生产多节点
关闭自我保护网络抖动时大面积误剔除生产谨慎关闭
服务下线直接杀进程调用方短时间打到旧实例做优雅下线
注册 IP 不可达控制台有实例但调用失败注册可访问地址
只看 Eureka 控制台忽略调用方本地缓存和过滤规则同时查调用方日志和 LoadBalancer
以为 Eureka 负责负载均衡找错问题层级Eureka 发现实例,LoadBalancer 选择实例

面试标准回答

Eureka 是什么,解决什么问题

text
Eureka 是 Spring Cloud Netflix 体系里的注册中心,解决微服务实例地址动态变化的问题。服务提供者启动后把服务名、IP、端口、状态和元数据注册到 Eureka Server,并定期发送心跳续约;服务消费者从 Eureka 拉取注册表并缓存到本地,通过服务名获取实例列表,再交给 Ribbon 或 Spring Cloud LoadBalancer 选择实例发起调用。它只负责服务发现,不负责真正的 HTTP 调用。

Eureka 为什么偏 AP

text
Eureka 更关注服务发现的可用性。它允许注册表在不同节点和客户端缓存中短时间不一致,也有自我保护机制,在大量心跳丢失时不立刻剔除实例,避免网络分区导致健康实例被误删。所以 Eureka 常说偏 AP。代价是调用方可能短时间拿到过期实例,因此业务还要配合超时、重试、熔断和优雅下线。

注册中心挂了还能调用吗

text
如果调用方本地已经缓存了实例列表,Eureka 短暂不可用时,已有服务之间可能还能继续调用。但新实例注册、故障实例剔除和实例变化感知会受影响。注册中心挂了不是完全没事,只是客户端缓存提高了短时间容错能力,所以生产仍然要部署 Eureka Server 集群。

Eureka 和 Nacos 怎么选

text
Eureka 是 Spring Cloud Netflix 老体系里的注册中心,老项目维护仍然常见;Nacos 属于 Spring Cloud Alibaba 体系,同时提供注册发现和配置中心,生态更适合 Alibaba 技术栈。新项目如果已经使用 Spring Cloud Alibaba,通常优先考虑 Nacos;维护老 Netflix 项目则要理解 Eureka 的注册、续约、剔除、自我保护和客户端缓存。

关联知识点

知识点跳转
Spring Cloud 主线Spring Cloud 从零到生产级掌握
服务调用链路服务调用链路
负载均衡Ribbon 与 LoadBalancer
OpenFeignFeign
NacosNacos
技术演进Spring Cloud 技术演进与选型
面试题Spring Cloud 面试知识点

本章小结

Eureka 的核心不是“控制台能看到服务”,而是服务注册发现的一整套机制:实例启动注册,定时续约,消费者拉取并缓存注册表,负载均衡按服务名选择实例,Server 通过租约剔除失效实例,自我保护在网络异常时避免误删。理解这些过程后,才能解释为什么注册中心挂了短时间还能调、为什么服务下线后还会被调用、为什么 Eureka 偏 AP,以及为什么新项目常用 Nacos 但老项目仍要会 Eureka。