1. 概述

本文将带你了解如何通过 Spring Cloud Netflix Eureka 实现 客户端服务发现

客户端服务发现允许服务间相互通信,无需硬编码主机名和端口。这种架构中唯一的"固定点"是服务注册中心,每个服务都必须向它注册。

⚠️ 这种方式有个缺点:所有客户端都必须实现特定逻辑与注册中心交互,这会导致实际请求前多一次网络往返。

使用 Netflix Eureka 时,每个客户端可同时作为服务器,将自身状态复制给连接的对等节点。换句话说,客户端从服务注册中心获取所有已连接节点的列表,并通过负载均衡算法向其他服务发起后续请求。

客户端必须向注册中心发送心跳信号,以表明自己的存活状态。

为达成目标,我们将实现三个微服务

  • 服务注册中心(Eureka Server)
  • 向注册中心注册的 REST 服务(Eureka Client)
  • 消费 REST 服务的 Web 应用(Spring Cloud Netflix Feign Client

2. Eureka Server

实现服务注册中心的 Eureka Server 非常简单,只需三步:

  1. 添加 spring-cloud-starter-netflix-eureka-server 依赖
  2. @SpringBootApplication 上添加 @EnableEurekaServer 注解
  3. 配置必要属性

我们一步步来操作。

首先创建 Maven 项目并添加依赖。注意所有项目都引入了 spring-cloud-starter-parent

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

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-parent</artifactId>
            <version>2023.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

最新 Spring Cloud 版本可在 Spring 官方文档 查看。

然后创建主应用类:

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

最后在 application.yml 中配置:

server:
  port: 8761
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

这里配置了应用端口(Eureka 服务器默认端口是 8761)。我们通过 registerWithEureka: false 告诉内置的 Eureka 客户端不要向自己注册,因为当前应用应作为服务器运行。

现在访问 http://localhost:8761 查看 Eureka 控制台,稍后我们将在其中检查已注册实例。当前页面会显示基础指标(如状态和健康指标):

Eureka 控制台截图

3. Eureka Client

要让 @SpringBootApplication 具备服务发现能力,需在 classpath 中添加 Spring Discovery Client(如 spring-cloud-starter-netflix-eureka-client),并在 @Configuration 类上添加 @EnableDiscoveryClient@EnableEurekaClient 注解。

✅ 注意:如果 classpath 已包含 spring-cloud-starter-netflix-eureka-client,该注解可省略。

为丰富客户端功能,我们还在 pom.xml 中添加 spring-boot-starter-web 并实现 REST 控制器。

先添加依赖(版本由 spring-cloud-starter-parent 自动管理):

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

实现主应用类:

@SpringBootApplication
@RestController
public class EurekaClientApplication implements GreetingController {

    @Autowired
    @Lazy
    private EurekaClient eurekaClient;

    @Value("${spring.application.name}")
    private String appName;

    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }

    @Override
    public String greeting() {
        return String.format(
          "Hello from '%s'!", eurekaClient.getApplication(appName).getName());
    }
}

以及 GreetingController 接口:

public interface GreetingController {
    @RequestMapping("/greeting")
    String greeting();
}

也可直接在 EurekaClientApplication 中声明映射,但接口形式便于在服务端和客户端间共享。

接下来配置 application.yml

  • 设置 spring.application.name 唯一标识客户端
  • 让 Spring Boot 自动选择随机端口(后续通过服务名访问)
  • 指定注册中心地址
spring:
  application:
    name: spring-cloud-eureka-client
server:
  port: 0
eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
  instance:
    preferIpAddress: true

这种配置便于后续服务扩展。启动客户端后,再次访问 http://localhost:8761 查看注册状态:

Eureka 客户端注册状态

4. Feign Client

最后实现使用 Spring Netflix Feign Client 消费服务的 Web 应用,完成三微服务架构。

Feign 本质是支持服务发现的 Spring RestTemplate,通过接口与接口通信。这些接口在运行时自动实现,且使用服务名而非 URL 替代服务地址。

❌ 如果不用 Feign,需手动注入 EurekaClient

@Autowired
private EurekaClient eurekaClient;

@RequestMapping("/get-greeting-no-feign")
public String greeting(Model model) {
    InstanceInfo service = eurekaClient
      .getApplication("spring-cloud-eureka-client")
      .getInstances()
      .get(0);

    String hostName = service.getHostName();
    int port = service.getPort();
    // ... 手动发起 HTTP 请求
}

RestTemplate 也可通过服务名访问 Eureka 客户端,但本文不展开。

为搭建 Feign Client 项目,在 pom.xml 添加四个依赖:

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

关键点:

  • spring-cloud-starter-feign 提供 Feign 支持
  • 需在 @Configuration 上添加 @EnableFeignClients 启用
  • 接口添加 @FeignClient("service-name") 即可注入控制器

最佳实践:将带有 @RequestMapping 的接口放在独立模块共享。服务端实现为 @Controller,客户端扩展为 @FeignClient

Feign Client 接口示例:

@FeignClient("spring-cloud-eureka-client")
public interface GreetingClient {
    @RequestMapping("/greeting")
    String greeting();
}

主应用类(兼控制器):

@SpringBootApplication
@EnableFeignClients
@Controller
public class FeignClientApplication {
    @Autowired
    private GreetingClient greetingClient;

    public static void main(String[] args) {
        SpringApplication.run(FeignClientApplication.class, args);
    }

    @RequestMapping("/get-greeting")
    public String greeting(Model model) {
        model.addAttribute("greeting", greetingClient.greeting());
        return "greeting-view";
    }
}

Thymeleaf 模板:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Greeting Page</title>
    </head>
    <body>
        <h2 th:text="${greeting}"/>
    </body>
</html>

application.yml 配置与客户端类似:

spring:
  application:
    name: spring-cloud-eureka-feign-client
server:
  port: 8080
eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}

启动服务后访问 http://localhost:8080/get-greeting,将显示:

Hello from SPRING-CLOUD-EUREKA-CLIENT!

5. 踩坑:TransportException: Cannot Execute Request on Any Known Server

运行 Eureka 服务器时,常遇到如下异常:

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server

根本原因通常是 application.yml 配置错误。Eureka 客户端有两个关键属性:

  • registerWithEureka:设为 true 时,服务器启动时内置客户端会尝试向 Eureka 注册
  • fetchRegistry:设为 true 时,内置客户端会尝试获取注册表

⚠️ 启动 Eureka 服务器时,我们不希望内置客户端注册自己!如果这些属性为 true(默认值),启动时内置客户端会尝试注册和获取注册表,但此时注册表尚未就绪,导致 TransportException

正确配置(Eureka 服务器专用):

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

6. 总结

本文介绍了如何使用 Spring Netflix Eureka Server 实现服务注册中心,并向其注册 Eureka Client

由于第 3 步的 Eureka Client 监听随机端口,离开注册中心无法确定自身位置。通过 Feign Client 和注册中心,即使服务位置变化,我们也能定位并消费 REST 服务。

最后我们了解了微服务架构中服务发现的整体应用。完整代码可在 GitHub 获取,其中还包含使用 docker-compose 创建容器的 Docker 相关文件。


原始标题:Introduction to Spring Cloud Netflix - Eureka