先理解一个核心问题:微服务实例之间如果不通过注册中心互相发现,服务地址只能写死在配置里,实例扩容、缩容或故障转移都会牵一发动全身。Eureka 做的事情是把每个服务实例的网络位置注册到一个共享注册表中,并让调用方根据服务名动态获取可用地址。Spring Cloud 对其做了封装,用几个注解和少量配置即可完成接入,但很多异常并不是代码写错,而是配置细节和自我保护机制没有搞清楚。

Eureka 的核心角色和工作原理
Eureka 体系里主要有两个角色:Eureka Server 和 Eureka Client。Server 是注册中心,维护着一张服务注册表,记录服务名、实例地址、端口、状态等元数据。Client 则嵌入在具体的业务服务中,它既可以是服务提供者,也可以是服务消费者。每个客户端启动后会向 Server 发送 REST 请求完成注册,并在此后周期性地发送心跳来续约,告诉 Server 自己还活着。如果 Server 长时间收不到某个实例的心跳,就会把该实例从注册表中剔除。
从数据同步角度看,Eureka Server 集群之间采用 Peer-to-Peer 异步复制。每个 Server 节点都保留一份注册表副本,客户端向任意一个节点注册或续约后,这个节点会把变更复制给其他节点。这种设计并不追求强一致性,而是优先保证可用性,因此 Eureka 通常被归为 AP 模型。即使部分节点之间暂时不同步,服务注册和查询仍然可以继续,不会因为一个节点故障就导致整个集群不可用。
这一点和 Zookeeper 形成明显差异。Zookeeper 强调 CP,一旦 Leader 不可用,整个集群可能在短时间内拒绝写请求,这对服务实例的注册场景并不友好。Eureka 则允许网络分区时保留部分过期实例,宁可让调用方短暂拿到不健康节点,也不轻易把整个注册能力停掉。自我保护机制正是这种设计思路的体现,这也是很多初学者在控制台看到红色告警时感到困惑的原因。
服务端搭建保姆级步骤
搭建 Eureka Server 先要创建一个 Spring Boot 工程,并在 pom 文件中引入 Eureka Server 依赖。依赖管理采用 Spring Cloud 的 BOM 统一控制版本,避免出现 Spring Boot 与 Spring Cloud 之间版本不兼容的问题。核心依赖只有一个,但要注意它必须配合 Spring Cloud 的依赖管理一起使用。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
依赖引入后,在启动类上添加 @EnableEurekaServer 注解,该注解会激活 Eureka Server 的自动配置,让当前 Spring Boot 应用以注册中心的角色对外提供服务。启动类本身不需要写额外逻辑,保持简洁即可。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
接下来配置 application.yml。单机演示环境下,必须把 register-with-eureka 和 fetch-registry 都设为 false,否则 Server 会尝试把自己当作客户端注册到别的节点,从而产生不必要的报错。defaultZone 的地址结尾要保留 /eureka/ 后缀,这是 Eureka 的固定路径,少写这一层会导致客户端注册失败。
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
启动完成后访问 http://127.0.0.1:8761/,能看到 Eureka 控制台。此时 Instances currently registered with Eureka 区域通常为空,因为还没有任何客户端注册上来。生产环境不建议只部署一个 Server 节点,至少应该部署两个节点并让它们互相注册,这样可以避免单个注册中心宕机后整个微服务体系失去服务发现能力。
客户端注册与调用示例
客户端工程的依赖同样很简单,引入 spring-cloud-starter-netflix-eureka-client 即可。客户端启动后会根据 defaultZone 找到 Eureka Server,并把自己注册上去。启动类上可以不加注解,因为只要存在 Client 依赖,Spring Boot 会自动完成注册发现相关配置;不过为了明确语义,很多团队习惯加上 @EnableDiscoveryClient。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
客户端配置中,spring.application.name 就是注册到 Eureka 的服务名,消费者会通过这个名称调用服务,因此必须保证唯一且有意义。为了排查方便,建议开启 prefer-ip-address,让注册列表直接展示 IP 而不是主机名,避免同一主机名对应多块网卡时出现误判。实例 ID 也可以自定义,防止同一台机器上启动多个实例时相互覆盖。
spring:
application:
name: order-service
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
注册成功后,要让一个服务通过服务名调用另一个服务,最直接的方式是使用 RestTemplate 并加上 @LoadBalanced 注解。这个注解会拦截 RestTemplate 的请求,把服务名解析成实际的 IP 和端口,同时提供客户端负载均衡能力。如果忘记加 @LoadBalanced,RestTemplate 会把服务名当作域名解析,最终抛出未知主机异常。
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;
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
调用端写业务代码时不再使用 IP 和端口,而是直接用服务名拼接 URL。比如订单服务要查询用户服务的信息,可以写成 http://user-service/user/1。Eureka 会把 user-service 解析成某个可用实例的地址,整个过程对业务代码透明。如果服务提供者下线或新增实例,只需要在注册中心更新元数据,调用方无需改动配置。
String url = "http://order-service/order/1"; String result = restTemplate.getForObject(url, String.class);
客户端启动后,Eureka Server 控制台应该能看到服务名、实例 ID、端口和 UP 状态。首次注册可能需要等待一到两个心跳周期,如果长时间没有出现,优先检查 defaultZone 是否拼写正确、是否以 /eureka/ 结尾,以及 Server 端口是否被防火墙拦截。
常见配置、问题排查与避坑指南
Eureka 的很多故障都集中在配置层,而不是业务代码层。下面整理了几组高频配置项,开发和排障时可以直接对照。
| 配置项 | 作用 | 常见设置 |
|---|---|---|
| eureka.client.register-with-eureka | 当前服务是否注册到 Eureka | 普通服务为 true,Server 单机模式为 false |
| eureka.client.fetch-registry | 是否从 Server 拉取注册表 | 消费者需要 true,Server 单机模式为 false |
| eureka.client.service-url.defaultZone | 指定 Eureka Server 地址 | http://127.0.0.1:8761/eureka/ |
| eureka.instance.prefer-ip-address | 注册时优先展示 IP | true |
| eureka.instance.lease-renewal-interval-in-seconds | 客户端心跳间隔 | 默认 30,开发环境可调小 |
| eureka.instance.lease-expiration-duration-in-seconds | Server 判定实例失效的时长 | 默认 90,需大于心跳间隔 |
| eureka.server.enable-self-preservation | 是否开启自我保护机制 | 生产建议开启,开发环境可关闭 |
| eureka.server.eviction-interval-timer-in-ms | Server 剔除失效实例的周期 | 默认 60000,单位毫秒 |
第一个高频问题是客户端已经启动,但 Eureka 控制台里始终看不到实例。除了 defaultZone 少写 /eureka/ 之外,还要检查 spring.application.name 是否缺失。没有应用名时,Eureka 会使用默认的 unknown 或生成名,导致服务看起来没有注册成功。再就是确认客户端和 Server 的时钟不要偏差太大,心跳过期判断依赖时间戳,时钟不同步会导致实例被误剔除。
第二个高频问题是 Eureka 控制台出现红色警告,提示自我保护模式已经开启。自我保护机制的设计逻辑是:如果 Server 在短时间内丢失了大量心跳,它会认为可能是网络分区导致自己与客户端之间的通信受阻,而不是所有客户端都真的宕机了。此时 Server 不会立即剔除过期实例,而是进入保护状态。开发环境和内网联调时经常遇到这个问题,因为网络抖动或频繁重启会让心跳比例骤降。开发环境可以在 Server 端设置 eureka.server.enable-self-preservation: false 并适当缩短剔除周期,方便快速观察实例上下线。
第三个高频问题是注册上来的 IP 不是期望地址。多网卡环境中,同一台机器可能同时有内网 IP、外网 IP、Docker 网桥 IP,Eureka 默认选择其中一个,结果消费者拿到的是不可达的地址。解决办法是设置 spring.cloud.inetutils.preferred-networks 指定优先网段,或者通过 spring.cloud.inetutils.ignored-interfaces 排除 Docker 等虚拟网卡。也可以直接在启动参数中指定 eureka.instance.ip-address,彻底固定注册地址。
第四个高频问题是服务下线后,消费者仍然可能请求到已停止的实例。客户端关闭时如果只是强制杀进程,Eureka 可能来不及收到取消注册请求,只能等待租约过期。默认租约过期是 90 秒,Server 剔除周期又是 60 秒,所以最长可能超过两分钟才消失。如果对下线敏感,可以调小 lease-expiration-duration-in-seconds 和 eviction-interval-timer-in-ms,但生产环境不建议调得过小,否则网络抖动会导致实例被频繁误删。
最后还要注意两点:一是 Eureka 1.x 已经停止大版本更新,但它依然稳定,适合 Spring Cloud 体系学习和小规模生产使用;如果新项目需要更丰富的注册中心能力,可以对比 Nacos、Consul 后再选型。二是生产环境一定要部署 Server 集群,单点注册中心一旦宕机,虽然客户端本地缓存还能支撑一段时间,但服务变更会立即失效,长期来看风险很高。集群配置的核心就是让多个 Server 节点互相注册,并把客户端的 defaultZone 写成逗号分隔的多个地址。
Spring CloudEureka服务注册与发现修改时间:2026-09-30 03:56:20