Eureka 服务注册与发现
Eureka 是 Spring Cloud Netflix 体系里的注册中心。它解决的核心问题是:微服务实例地址会变化,调用方不能把 IP 和端口写死。
单体应用里,订单模块调用库存模块只是进程内方法调用。拆成微服务后,订单服务、库存服务、资产服务、字典服务可能分别部署多个实例,而且实例会扩容、缩容、重启、下线。如果调用方还写死 http://10.1.2.3:8080,只要实例迁移或故障,调用就会失败。
Eureka 的职责就是维护一张“服务名 -> 实例列表”的注册表,让服务提供者启动后把自己注册上来,让服务消费者按服务名发现可用实例,再交给 Ribbon 或 Spring Cloud LoadBalancer 选择一个实例调用。
阅读入口:跟着一次实例生命周期走
按“注册 → 续约 → 拉取缓存 → 选择实例 → 剔除”阅读。先理解 Server 和 Client 的边界,再看自我保护与高可用,最后用服务发现排查验证本地缓存与服务端注册表。
核心原理:租约、缓存与最终一致
Eureka 不把每次业务请求转发到 Server,而是采用“客户端注册 + 定时续约 + 客户端拉取注册表”的控制面模型。服务端用租约判断实例是否仍然活跃,消费者把注册表缓存在本地,因此注册中心短暂不可用时,已有调用仍可能使用旧快照;代价是实例剔除存在传播延迟。
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 注册表和消费者本地实例快照。
学习目标
学完本页你要能回答:
- 注册中心到底解决什么问题。
- Eureka Server 和 Eureka Client 分别做什么。
- 服务注册、续约、拉取注册表、剔除实例的完整流程。
- 消费者为什么会有本地注册表缓存。
- Eureka 为什么偏 AP,不追求强一致。
- 自我保护机制为什么存在,不理解会造成什么误判。
- Eureka 和 Nacos、Consul、ZooKeeper 的区别。
- 注册中心挂了为什么短时间还能调用。
- 注册中心有实例但 Feign 仍报无实例怎么排查。
- 老项目维护 Eureka,新项目迁移 Nacos 时要注意什么。
为什么需要注册中心
先看没有注册中心时会发生什么。
flowchart TD
A["订单服务"] --> B["写死库存服务 IP:Port"]
B --> C{"库存服务是否扩容/重启/迁移"}
C -- "否" --> D["暂时能调用"]
C -- "是" --> E["地址失效或实例不均衡"]
E --> F["调用失败、发布困难、无法弹性扩容"]写死地址的问题:
| 问题 | 后果 |
|---|---|
| 实例重启后地址变化 | 调用方配置要改 |
| 服务扩容 | 调用方不知道新实例 |
| 服务缩容或宕机 | 调用方仍可能打到旧地址 |
| 多实例负载均衡 | 调用方要自己维护实例列表 |
| 多环境部署 | dev/test/prod 地址混乱 |
| 灰度和机房路由 | 很难在调用方硬编码维护 |
有注册中心后,调用方只依赖服务名:
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 注册发现全过程
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。
注册信息通常包括:
| 信息 | 示例 | 用途 |
|---|---|---|
appName | inventory-service | 服务名 |
instanceId | host:inventory-service:8081 | 实例唯一标识 |
ipAddr | 10.1.2.11 | 调用地址 |
port | 8081 | 调用端口 |
status | UP | 实例状态 |
metadata | version=v1, zone=shanghai | 灰度、机房、权重等扩展信息 |
leaseInfo | 续约间隔、过期时间 | 判断实例是否存活 |
可以理解成:
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:服务提供者
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: v1prefer-ip-address: true 表示优先把 IP 注册上去。生产环境里要确认注册出去的 IP 是其他服务能访问到的地址,而不是容器内部不可达地址或 127.0.0.1。
服务续约:为什么要心跳
服务注册上来不代表永远可用。实例可能宕机、网络断开、容器被杀、发布重启。Eureka 用续约心跳维护实例租约。
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-seconds | Server 多久没收到心跳认为过期 |
示例:
eureka:
instance:
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90含义是:客户端大约每 30 秒续约一次;如果 90 秒没有续约,Server 才考虑剔除。
为什么不是 1 秒没心跳就剔除?因为网络抖动、GC 暂停、短暂 CPU 抢占都可能造成心跳延迟。过于敏感会导致健康实例被误删,调用方实例列表频繁变化。
服务发现:消费者为什么要本地缓存
消费者不会每次调用都实时访问 Eureka。它通常会周期性拉取注册表并缓存到本地。
flowchart TD
A["消费者启动"] --> B["从 Eureka 拉全量或增量注册表"]
B --> C["保存到本地缓存"]
D["业务发起调用"] --> E["按服务名查本地实例列表"]
E --> F["负载均衡选择实例"]
F --> G["HTTP 调用目标服务"]
H["定时刷新注册表"] --> C为什么要本地缓存?
| 原因 | 说明 |
|---|---|
| 降低注册中心压力 | 每次调用都查 Eureka,注册中心会被打爆 |
| 提高调用性能 | 本地内存查实例比远程查询快 |
| 提高可用性 | Eureka 短暂不可用时,消费者还能用旧缓存继续调用 |
| 支持客户端负载均衡 | LoadBalancer 基于本地实例列表选实例 |
这也带来一个重要后果:服务上下线不是瞬间对所有消费者生效。注册表变化需要经过注册中心感知、同步、消费者刷新本地缓存、连接池释放旧连接等多个步骤。
调用链路:Eureka 和 Feign/LoadBalancer 的关系
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 控制台。你要确认:
- 调用方本地缓存里有没有这个服务。
- 服务名是否一致。
- namespace、profile、环境是否一致。
- 实例是否被健康、灰度、区域过滤掉。
- 客户端缓存是否及时刷新。
- HTTP Client 是否还复用旧连接。
Eureka Server 高可用
生产环境不能只部署一个 Eureka Server。常见做法是多个 Eureka Server 相互注册,彼此复制注册信息。
flowchart TD
A["Eureka Server A"] <--> B["Eureka Server B"]
B <--> C["Eureka Server C"]
C <--> A
D["服务实例"] --> A
D --> B
E["消费者"] --> A
E --> CEureka Server 集群不是强一致数据库集群。节点之间注册信息同步可能有延迟,所以短时间内不同 Server 看到的注册表可能不同。
配置示例:
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/单机学习时可以设置:
eureka:
client:
register-with-eureka: false
fetch-registry: false但生产多节点通常要互相注册和拉取。
自我保护机制
Eureka 的自我保护机制是很多人面试会背、但不理解的点。
当 Eureka 在短时间内发现大量实例心跳丢失时,它不会立刻剔除所有这些实例。因为这可能不是服务真的都挂了,而是 Eureka Server 和服务实例之间出现网络分区。
flowchart TD
A["大量实例心跳丢失"] --> B{"可能是什么原因"}
B --> C["实例真的大面积宕机"]
B --> D["网络分区或 Eureka 自身网络异常"]
D --> E["如果立刻剔除会误删健康实例"]
E --> F["进入自我保护,保留注册表"]自我保护的思想是:在不确定是服务挂了还是网络抖了时,宁愿保留可能过期的实例,也不要把整个注册表清空。
自我保护的收益和风险
| 维度 | 说明 |
|---|---|
| 收益 | 避免网络抖动时大面积误剔除,保证注册中心可用性 |
| 风险 | 注册表可能保留已经宕机的实例,调用方可能打到坏实例 |
所以 Eureka 更偏 AP:优先保证注册中心可用,允许短时间注册信息不完全准确。
自我保护能不能关闭
开发或测试环境有时会关闭自我保护,方便快速看到实例下线:
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 对比
| 对比项 | Eureka | Nacos | Consul | ZooKeeper |
|---|---|---|---|---|
| 常见生态 | Spring Cloud Netflix | Spring Cloud Alibaba | 多语言基础设施 | 分布式协调 |
| 主要能力 | 注册发现 | 注册发现 + 配置中心 | 注册发现 + KV + 健康检查 | 协调、注册、配置存储 |
| 一致性倾向 | 偏 AP | 支持临时/永久实例,能力更丰富 | 偏 CP,健康检查强 | 偏 CP |
| 配置中心 | 不提供 | 内置 | KV 可承载 | 可实现但非专用配置中心 |
| 健康检查 | 客户端心跳为主 | 心跳/服务端探测等 | 健康检查成熟 | 临时节点会话 |
| 新项目推荐度 | 老项目维护常见 | Alibaba 生态常见 | 多语言/基础设施常见 | 协调场景常见 |
面试不要简单说“Eureka 过时了”。更成熟的回答是:
Eureka 是 Spring Cloud Netflix 老体系里的注册中心,老项目维护仍然常见。新项目如果使用 Spring Cloud Alibaba,常会选择 Nacos,因为它同时提供注册发现和配置中心,也有更活跃的生态。选型不是看新旧,而是看团队技术栈、配置中心需求、多语言支持、运维能力和迁移成本。商业场景:医疗数据采集平台
假设平台拆成这些服务:
| 服务 | 职责 |
|---|---|
gateway-service | 统一入口、鉴权、限流 |
collect-service | 医院数据采集任务 |
asset-service | 医疗资产管理 |
dict-service | 字典和编码映射 |
auth-service | 用户认证和权限 |
调用链路可能是:
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 的价值:
- 采集服务不需要写死字典服务和资产服务 IP。
- 字典服务扩容后,采集服务能发现新实例。
- 字典服务某实例宕机后,后续注册表刷新会逐步剔除。
- 注册中心短暂不可用时,采集服务可以用本地缓存继续调用。
风险也要说清:
- 服务下线有传播延迟,调用方可能短时间打到旧实例。
- 注册表可能短时间不一致,必须配合超时、重试、熔断。
- 不能把 Eureka 当强一致健康判断系统。
- 灰度、同机房优先、权重路由要结合元数据和负载均衡扩展。
配置 Demo:Eureka Server
依赖示例:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>启动类:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}单机学习配置:
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
依赖示例:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>服务提供者配置:
server:
port: 8081
spring:
application:
name: inventory-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true启动类:
@SpringBootApplication
public class InventoryApplication {
public static void main(String[] args) {
SpringApplication.run(InventoryApplication.class, args);
}
}较新的 Spring Cloud 中,只要引入 Eureka Client 依赖并配置注册中心地址,通常不一定必须显式写 @EnableEurekaClient。老项目里看到这个注解也正常。
Feign 调用 Demo
库存服务提供接口:
@RestController
@RequestMapping("/inventory")
public class InventoryController {
@GetMapping("/{skuId}")
public InventoryDTO getInventory(@PathVariable Long skuId) {
return new InventoryDTO(skuId, 100);
}
}订单服务 Feign Client:
@FeignClient(name = "inventory-service", path = "/inventory")
public interface InventoryClient {
@GetMapping("/{skuId}")
InventoryDTO getInventory(@PathVariable("skuId") Long skuId);
}调用方:
@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("库存不足");
}
}
}这段代码看起来像本地接口调用,但底层链路是:
OrderService -> Feign 代理 -> LoadBalancer -> Eureka 本地注册表 -> 选择实例 -> HTTP 请求所以必须配置超时、错误处理和熔断,不能把 Feign 当普通本地方法。
生产排查:注册中心有实例但调用失败
现象一:Feign 报 No instances available
排查链路:
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-service 和 inventory_service 写法不一致 |
| 注册到了另一个 Eureka | dev/test/prod 注册中心混了 |
| 调用方缓存未刷新 | 注册表拉取有周期 |
| 实例状态不是 UP | 被标记下线或健康异常 |
| 灰度过滤后为空 | version、zone、metadata 过滤导致无实例 |
| 网络不通 | 调用方访问不到注册实例的 IP |
现象二:服务下线后还被调用
这是注册发现系统的正常传播窗口。
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 是什么,解决什么问题
Eureka 是 Spring Cloud Netflix 体系里的注册中心,解决微服务实例地址动态变化的问题。服务提供者启动后把服务名、IP、端口、状态和元数据注册到 Eureka Server,并定期发送心跳续约;服务消费者从 Eureka 拉取注册表并缓存到本地,通过服务名获取实例列表,再交给 Ribbon 或 Spring Cloud LoadBalancer 选择实例发起调用。它只负责服务发现,不负责真正的 HTTP 调用。Eureka 为什么偏 AP
Eureka 更关注服务发现的可用性。它允许注册表在不同节点和客户端缓存中短时间不一致,也有自我保护机制,在大量心跳丢失时不立刻剔除实例,避免网络分区导致健康实例被误删。所以 Eureka 常说偏 AP。代价是调用方可能短时间拿到过期实例,因此业务还要配合超时、重试、熔断和优雅下线。注册中心挂了还能调用吗
如果调用方本地已经缓存了实例列表,Eureka 短暂不可用时,已有服务之间可能还能继续调用。但新实例注册、故障实例剔除和实例变化感知会受影响。注册中心挂了不是完全没事,只是客户端缓存提高了短时间容错能力,所以生产仍然要部署 Eureka Server 集群。Eureka 和 Nacos 怎么选
Eureka 是 Spring Cloud Netflix 老体系里的注册中心,老项目维护仍然常见;Nacos 属于 Spring Cloud Alibaba 体系,同时提供注册发现和配置中心,生态更适合 Alibaba 技术栈。新项目如果已经使用 Spring Cloud Alibaba,通常优先考虑 Nacos;维护老 Netflix 项目则要理解 Eureka 的注册、续约、剔除、自我保护和客户端缓存。关联知识点
| 知识点 | 跳转 |
|---|---|
| Spring Cloud 主线 | Spring Cloud 从零到生产级掌握 |
| 服务调用链路 | 服务调用链路 |
| 负载均衡 | Ribbon 与 LoadBalancer |
| OpenFeign | Feign |
| Nacos | Nacos |
| 技术演进 | Spring Cloud 技术演进与选型 |
| 面试题 | Spring Cloud 面试知识点 |
本章小结
Eureka 的核心不是“控制台能看到服务”,而是服务注册发现的一整套机制:实例启动注册,定时续约,消费者拉取并缓存注册表,负载均衡按服务名选择实例,Server 通过租约剔除失效实例,自我保护在网络异常时避免误删。理解这些过程后,才能解释为什么注册中心挂了短时间还能调、为什么服务下线后还会被调用、为什么 Eureka 偏 AP,以及为什么新项目常用 Nacos 但老项目仍要会 Eureka。
