广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何用Spring Boot整合Eureka实现服务注册与发现?

在微服务架构中,服务实例的数量和位置会频繁变化,传统单体应用里写死主机名或端口的方式已经无法适应。Spring Cloud Netflix Eureka 提供了一套完整的服务注册与发现解决方案,服务提供者启动时向注册中心登记自己的元数据,消费者定期拉取注册表并根据服务名定位到实际节点。本文以 Spring Boot 为基础,完整演示 Eureka Server 与 Eureka Client 的整合过程。

如何用Spring Boot整合Eureka实现服务注册与发现?

接下来的步骤会覆盖服务端搭建、客户端接入、消费者负载均衡调用以及常见故障排查,代码示例基于 Maven 和 Spring Boot 2.x 编写,但核心思路同样适用于 Spring Boot 3.x 和 Spring Cloud 新版。

一、搭建 Eureka 服务端

Eureka Server 是整个注册中心的核心,它维护着一张服务注册表,记录所有已注册服务的实例地址、端口、状态以及最后心跳时间。搭建服务端的第一步是创建一个 Spring Boot 工程,并引入 spring-cloud-starter-netflix-eureka-server 依赖。如果你的项目使用 Maven 管理依赖,可以在 pom.xml 中加入如下配置:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR12</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

这里使用 dependencyManagement 统一管理 Spring Cloud 的版本,避免各个组件版本不一致导致的冲突。引入依赖后,启动类上需要标注 @EnableEurekaServer 注解,它会让 Spring Boot 应用自动装配 Eureka Server 的相关组件,并暴露注册中心需要的 REST 接口。

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);
    }
}

启动类就绪后,配置文件同样不能忽略。Eureka Server 默认也会尝试把自己注册到注册中心,但作为服务端它没有必要注册自身,所以需要关闭客户端行为。下面是一份适合本地开发和测试的 application.yml 配置:

server:
  port: 8761
spring:
  application:
    name: eureka-server
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 5000

其中 register-with-eureka 和 fetch-registry 都设置为 false,表示这个实例既不会注册自己,也不会从别的节点拉取注册表。端口沿用 Eureka 官方默认的 8761,启动后访问本地的 8761 端口,就能看到简洁的控制台页面。控制台中暂时没有实例,因为还没有任何客户端接入。

配置里额外关闭了自我保护机制,并将淘汰任务的执行间隔设置成 5 秒。这样在开发阶段,服务下线后能被更快地感知到,方便调试。但自我保护机制在生产环境中不应该轻易关闭,它的作用是在网络抖动时保护服务实例不被误删,后文会专门解释。

二、将服务提供者注册到 Eureka

要让一个 Spring Boot 应用成为服务提供者,需要引入 Eureka Client 相关依赖,并在配置中指定注册中心地址。新建一个名为 order-service 的工程,在 pom.xml 中加入如下依赖:

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

客户端启动类上可以不加注解,因为只要类路径中存在 Eureka Client 并且配置了正确的注册中心地址,Spring Boot 就会自动注册。如果你希望显式开启,可以在启动类上标注 @EnableEurekaClient 或 @EnableDiscoveryClient。接下来配置应用名和注册中心地址:

server:
  port: 8081
spring:
  application:
    name: order-service
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}

这里最关键的是 spring.application.name,它决定了服务在注册中心中的名字。消费者在发起调用时,使用的就是这个逻辑名称,而不是具体的 IP 和端口。配置 prefer-ip-address 为 true 可以让 Eureka 优先使用 IP 地址注册,避免主机名解析失败。自定义 instance-id 则是为了避免同一服务多实例部署时发生 ID 覆盖。

完成配置后,给这个服务添加一个简单的 REST 接口,方便后续消费者调用:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
    @GetMapping("/orders")
    public String getOrder() {
        return "order-service instance: " + System.currentTimeMillis();
    }
}

启动 order-service 后,Eureka Server 控制台很快会出现一条注册记录,状态为 UP。这是因为客户端启动时会立即向注册中心发送一次注册请求,并且之后每隔 30 秒发送一次心跳续约。如果注册中心在一定时间内没有收到心跳,就会判断该实例不可用并将其从注册表中剔除。

除了注册和续约,服务提供者还可以通过 actuator 暴露健康检查端点,让 Eureka 根据真实的健康状态决定是否保留实例。默认情况下,Eureka 只根据心跳判断存活,但如果引入 spring-boot-starter-actuator 并配置 eureka.client.healthcheck.enabled=true,注册中心就会调用健康检查端点,这样数据库连接池不可用等内部故障也能被及时感知。

三、服务消费者通过 Eureka 实现服务发现

消费者同样需要引入 Eureka Client 依赖,但它的角色变成了服务发现者。它不会提供业务接口,而是从注册中心拉取服务列表,再把请求转发给具体的提供者实例。为了实现负载均衡调用,通常会使用 RestTemplate 并配合 @LoadBalanced 注解。新建一个 user-service 工程,在启动类中注册一个带负载均衡能力的 RestTemplate:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

在需要调用其他服务的地方,直接注入这个 RestTemplate,然后把 URL 的主机部分写成服务名即可,不需要关心具体地址:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class UserController {
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("/user-order")
    public String callOrder() {
        String url = "http://order-service/orders";
        return restTemplate.getForObject(url, String.class);
    }
}

当 user-service 调用 http://order-service/orders 时,@LoadBalanced 注解会让 Spring Cloud 使用负载均衡器拦截请求,将服务名 order-service 解析为注册中心里可用的实例地址。默认的负载均衡策略是轮询,多个实例依次接收请求。如果某个实例突然宕机,负载均衡器会结合健康检查结果跳过它,从而避免调用失败。

消费者的配置文件同样需要指向 Eureka Server,并且要开启服务发现能力。与提供者不同,消费者自身的注册行为可以根据需要开启或关闭。如果只是单纯调用其他服务,可以用 register-with-eureka: false 关闭自身注册,但保留 fetch-registry: true 来拉取服务列表。

server:
  port: 8082
spring:
  application:
    name: user-service
eureka:
  client:
    register-with-eureka: false
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/

配置完成后启动两个服务,访问 /user-order 接口,就能看到返回结果中携带着 order-service 实例的时间戳,说明调用链路已经通过 Eureka 打通。此时如果再启动一个 order-service 的实例,端口改为 8083,注册中心会自动感知,负载均衡也会在两个实例之间轮流转发请求。

四、自我保护机制与常见问题排查

Eureka 的自我保护机制是很多开发者最初遇到困惑的地方。默认情况下,Eureka Server 会统计每分钟的心跳失败比例,如果 15 分钟内低于 85%,就会认为出现了网络分区或大规模宕机,从而停止剔除实例,并进入自我保护模式。此时注册中心虽然仍能接受新注册,但不会删除任何过期实例,控制台会出现红色警告。这不是 bug,而是一种高可用设计,但在开发环境中容易让人误以为服务下线不生效。

开发测试可以关闭自我保护机制,并缩短心跳与剔除间隔,让注册中心更快地清理无效节点:

eureka:
  instance:
    lease-renewal-interval-in-seconds: 10
    lease-expiration-duration-in-seconds: 30
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 5000

其中 lease-renewal-interval-in-seconds 控制客户端发送心跳的频率,lease-expiration-duration-in-seconds 表示超过多久没有收到心跳就判定实例过期。生产环境中不要将这两个值设得过小,否则网络稍有波动就会导致大量实例被误摘除。生产环境更推荐保留自我保护机制,配合监控告警排查真正的网络问题。

另一个常见问题是实例 ID 冲突。如果多个服务实例使用相同的 instance-id,后注册的实例会覆盖前面的记录,注册中心里始终只显示一个实例。解决办法是在配置中拼接 IP 和端口,形成唯一标识。此外,服务下线后注册中心不会立即删除记录,需要等待客户端主动取消注册,或者等心跳过期后由淘汰任务清理,这个过程可能存在几十秒延迟。如果调用方仍然拿到旧地址,就会产生短时间的连接失败,可以在消费者端设置重试机制来缓解。

还有一些容易被忽略的细节,例如 defaultZone 的地址结尾必须包含 /eureka/,否则客户端无法完成注册;服务端和客户端的 Spring Cloud 版本必须匹配,否则可能出现注解找不到或配置项不生效的情况;如果服务部署在容器环境中,需要正确设置 IP 注册方式,避免注册的是容器内部地址而导致其他服务不可达。掌握了这些排查思路,大部分 Eureka 相关的集成问题都能快速定位。

广告位 · 文章底部(后台-模块管理替换此区域)