目录

一、前言:微服务架构下的“导航仪”

二、Eureka 概述:它到底解决了什么问题?

2.1 什么是 Eureka

2.2 Eureka 核心概念深度解析

2.3 Eureka 架构图解

三、环境准备:工欲善其事,必先利其器

3.1 技术栈版本选型

3.2 项目结构规划

四、搭建 Eureka Server(注册中心)

4.1 添加依赖

4.2 启动类配置

4.3 配置文件详解

4.4 启动验证

五、搭建 Eureka Client(服务提供者)

5.1 添加依赖

5.2 启动类

5.3 配置文件

5.4 业务代码

5.5 验证注册

六、搭建服务消费者(order-service)

6.1 添加依赖

6.2 RestTemplate 配置

6.3 服务间调用

6.4 服务调用原理

七、Eureka 工作原理深入

7.1 服务注册流程

7.2 心跳机制(续约)

7.3 服务列表拉取

八、Eureka Server 高可用集群:告别单点故障

8.1 为什么需要集群

8.2 集群原理

8.3 搭建集群

8.4 客户端配置

九、常见问题与解决方案:踩坑实录

9.1 服务注册失败

9.2 服务调用 404

9.3 自我保护模式

9.4 IP 地址显示问题

十、总结与展望

核心知识点回顾

Eureka 优缺点分析

学习收获


一、前言:微服务架构下的“导航仪”

大家好,我是你们的老朋友。今天咱们不聊虚的,直接上手干货——Spring Cloud Eureka 服务注册与发现

随着互联网业务的飞速发展,单体架构(Monolithic)逐渐显得力不从心。想象一下,一个庞大的电商系统,如果所有功能(用户、订单、商品、支付)都耦合在一个 WAR 包里,一旦某个模块出现内存泄漏,整个系统都得挂掉。更重要的是,团队协作困难,部署周期长,扩容也不灵活。于是,微服务架构应运而生。

我们将大系统拆分成几十甚至上百个小服务。但问题来了:服务这么多,它们在哪里?怎么找到它们?

在单体时代,我们直接硬编码 IP 和端口调用接口。但在微服务时代,服务实例动态变化(扩容、缩容、宕机),硬编码 IP 简直就是灾难。这时候,服务注册与发现(Service Registration and Discovery) 机制就成了微服务架构的“导航仪”。

Eureka 作为 Netflix 开源的核心组件,曾经是 Spring Cloud 生态事实上的标准。虽然 сейчас(现在)它已进入维护模式,但在大量存量系统中依然扮演着至关重要的角色。理解 Eureka,不仅是为了维护旧系统,更是为了理解服务治理的核心思想。

本文学习目标:

  • 理解 Eureka 服务注册与发现的核心概念与 CAP 理论背景
  • 掌握 Eureka Server 和 Eureka Client 的搭建方法与配置细节
  • 学会使用 RestTemplate + LoadBalancer 实现服务间调用
  • 深入了解 Eureka 心跳、续约、剔除的工作原理
  • 了解 Eureka 高可用集群的搭建方法与故障转移机制
  • 掌握生产环境常见问题的排查思路

二、Eureka 概述:它到底解决了什么问题?

2.1 什么是 Eureka

Eureka 是 Netflix 公司开源的一个基于 REST 服务的服务注册与发现框架。它的名字来源于希腊神话,意为“发现”。在 AWS 云环境中,中间层服务的位置是动态变化的,Eureka 的主要目的就是定位这些服务,从而实现负载均衡和故障转移。

在 Spring Cloud 体系中,Eureka 扮演着“电话簿”的角色。服务提供者把电话号码(IP+ 端口)登记到电话簿(Eureka Server),服务消费者需要打电话时,先去电话簿查号码,然后再拨通。

核心架构:CS 架构(Client-Server)

  • Eureka Server:服务注册中心。它是一个独立的微服务,负责接收服务注册、维护服务列表、提供服务发现查询。它本身也是高可用的,可以集群部署。
  • Eureka Client:服务提供者或服务消费者。任何需要注册到中心或被中心发现的服务都是 Client。它通过心跳机制告诉 Server 自己还“活着”。

2.2 Eureka 核心概念深度解析

为了让大家理解得更透彻,我们把 Eureka 的生命周期拆解成几个关键动作。下表整理了核心概念,建议收藏:

概念

英文标识

说明

默认参数

重要性

服务注册

Register

服务启动时向 Eureka Server 注册自己的信息(IP、端口、服务名、健康状态等)

启动即注册

⭐⭐⭐⭐⭐

服务续约

Renew

客户端每间隔一段时间发送一次心跳,告知 Server 自己仍在运行,防止被误删

30 秒

⭐⭐⭐⭐⭐

服务下线

Cancel

服务正常关闭时主动发送下线请求,告知 Server 从注册表中移除自己

关闭时触发

⭐⭐⭐

服务剔除

Eviction

超过一定时间未收到心跳,Server 将服务从注册表中剔除,标记为不可用

90 秒

⭐⭐⭐⭐

服务发现

Fetch Registry

客户端定期从 Server 拉取服务列表,缓存到本地,减少网络请求

30 秒

⭐⭐⭐⭐⭐

自我保护

Self Preservation

当网络故障导致大量心跳丢失时,保护注册表不被清空,宁可保留坏节点也不误删

默认开启

⭐⭐⭐⭐

2.3 Eureka 架构图解

为了更直观地理解,我们来看一张架构交互图。这里我为大家准备了一个时序图的 UML 代码,大家可以直观地看到服务启动后的交互流程。

图解说明:

  1. 注册:服务启动后,立刻向服务器报到。
  2. 续约:为了证明我没挂,每隔 30 秒喊一声“我到”。
  3. 拉取:为了知道别人在哪,每隔 30 秒问一次“都有谁”。
  4. 缓存:为了快,把问到的名单存本地,调用时直接查本地,不用每次都问服务器。

三、环境准备:工欲善其事,必先利其器

3.1 技术栈版本选型

微服务版本兼容性是个大坑,选错版本会导致依赖冲突甚至启动失败。本文基于最新的 Spring Boot 3.x 体系,这也是目前企业新项目的趋势。

组件

版本

说明

备注

Spring Boot

3.1.6

基础框架

支持 Java 17+,性能提升显著

Spring Cloud

2022.0.3

微服务套件

对应 Spring Boot 3.x 版本

Java

17

JDK 版本

LTS 版本,推荐生产使用

MySQL

8.0.33

数据库

存储业务数据

MyBatis

3.0.3

ORM 框架

持久层框架

Netflix Eureka

4.x

服务注册中心

Spring Cloud Netflix 维护版

专家提示:

注意,Spring Boot 3.x 不再支持 Java 8,必须使用 Java 17 或更高版本。同时,Spring Cloud 2022.x 版本中,Netflix 组件处于维护状态,但依然可用。如果是全新项目,建议评估是否直接使用 Spring Cloud Alibaba (Nacos)。

3.2 项目结构规划

一个清晰的工程结构能让后续维护事半功倍。我们采用 Maven 多模块管理。

spring-cloud-eureka/
├── pom.xml                           # 父项目 POM,管理依赖版本
├── eureka-service/                   # Eureka 注册中心模块
│   ├── src/main/java/...
│   └── src/main/resources/...
├── product-service/                  # 商品服务模块(服务提供者)
│   ├── src/main/java/...
│   └── src/main/resources/...
└── order-service/                    # 订单服务模块(服务消费者)
    ├── src/main/java/...
    └── src/main/resources/...

父项目 POM 关键配置:

这里我们统一管理版本号,子模块无需重复声明版本,避免冲突。

<properties>
    <java.version>17</java.version>
    <spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

配置解析:

  • <spring-cloud.version>:这是 Spring Cloud 的版本号,不是具体的 jar 包版本。它对应着一个庞大的依赖集合(BOM),确保所有组件版本兼容。
  • <scope>import</scope>:表示导入依赖管理,而不是直接引入 jar 包。

四、搭建 Eureka Server(注册中心)

注册中心是微服务的“大脑”,必须优先搭建。

4.1 添加依赖

eureka-service/pom.xml 中添加核心依赖。注意,这里只需要引入 Server 的 starter。

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

4.2 启动类配置

启动类非常简洁,核心在于一个注解。

@SpringBootApplication
@EnableEurekaServer  // 开启 Eureka Server 功能
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

关键注解说明:

  • @EnableEurekaServer:这是核心。它导入了 Eureka Server 的相关配置类,启动了内嵌的 Tomcat 以及注册中心的逻辑接口。没有它,这只是一个普通的 Spring Boot 应用。

4.3 配置文件详解

配置文件是重中之重,很多坑都出在这里。

server:
  port: 10010

spring:
  application:
    name: eureka-server  # 应用名称

eureka:
  instance:
    hostname: localhost
  client:
    # Eureka Server 不需要向其他 Server 注册
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

配置项深度剖析:

  1. server.port: 10010:注册中心端口。生产环境建议不要使用默认 8080,避免冲突。
  2. spring.application.name:服务名。在 Eureka 面板上显示的名字。
  3. eureka.client.register-with-eureka: false关键点。因为当前是单点注册中心,它不需要把自己注册到自己身上。如果是在集群模式下,这个值需要改为 true,以便节点间互相注册。
  4. eureka.client.fetch-registry: false关键点。同样,单点模式下不需要拉取其他服务的注册信息。
  5. defaultZone:指定注册中心地址。这里使用了变量替换,方便后续集群配置修改。

4.4 启动验证

启动 EurekaServerApplication,访问 http://localhost:10010

你会看到一个简洁的网页,显示"Instances currently registered with Eureka"。初始状态下,下面应该是空的。这就是我们的服务治理控制台,后续所有服务的健康状态都会在这里展示。


五、搭建 Eureka Client(服务提供者)

接下来搭建 product-service 商品服务,作为服务提供者向 Eureka 注册。这是业务流量的源头。

5.1 添加依赖

除了 Web 依赖,必须引入 Eureka Client 依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
</dependencies>

5.2 启动类

@SpringBootApplication
@EnableDiscoveryClient  // 开启服务发现客户端
public class ProductServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductServiceApplication.class, args);
    }
}

关键注解说明:

  • @EnableDiscoveryClient:这是一个通用注解。在 Eureka 环境下,它等同于 @EnableEurekaClient。它的作用是告诉 Spring Cloud,这个应用需要参与服务发现,启动时会自动向注册中心注册。

5.3 配置文件

server:
  port: 9090

spring:
  application:
    name: product-service  # 服务名称,注册到 Eureka 的名字
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:
    map-underscore-to-camel-case: true  # 自动驼峰转换

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10010/eureka/  # Eureka Server 地址

避坑指南:

  • 服务名大小写:Eureka 默认会将服务名转为大写存储。虽然调用时通常不敏感,但为了规范,建议配置文件和代码中保持一致。
  • 地址配置defaultZone 必须指向真实的 Eureka Server 地址,否则客户端无法注册。

5.4 业务代码

这里我们简化展示核心逻辑,重点在于 Controller 的暴露。

实体类:

@Data
public class ProductInfo {
    private Integer id;
    private String productName;
    private Integer productPrice;
    private Integer state;
    private Date createTime;
}

Mapper:

@Mapper
public interface ProductMapper {
    @Select("select * from product_detail where id = #{productId}")
    ProductInfo selectProductById(Integer productId);
}

Controller:

@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    private ProductService productService;

    @RequestMapping("/{productId}")
    public ProductInfo getProductById(@PathVariable("productId") Integer productId) {
        return productService.selectProductById(productId);
    }
}

5.5 验证注册

启动 product-service 后,刷新 Eureka 控制台。

可以看到 PRODUCT-SERVICE 已注册到 Eureka。状态显示为 UP,表示健康。如果你看到 DOWN,说明心跳超时或网络不通。


六、搭建服务消费者(order-service)

服务消费者是微服务调用的发起方。这里我们重点讲解如何实现负载均衡调用

6.1 添加依赖

在原有依赖基础上增加负载均衡组件。注意 Spring Cloud 2022 版本后,Ribbon 已被移除,改用 Spring Cloud LoadBalancer。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>

6.2 RestTemplate 配置

我们需要配置一个特殊的 RestTemplate Bean。

@Configuration
public class BeanConfig {
    @Bean
    @LoadBalanced  // 开启负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

关键注解说明:

  • @LoadBalanced:这是灵魂注解。它拦截了 RestTemplate 的请求。当你使用服务名(如 http://PRODUCT-SERVICE)作为域名时,拦截器会介入,从本地缓存获取该服务名对应的所有实例列表,然后通过负载均衡算法(默认轮询)选择一个实例,替换掉服务名,发起真正的 HTTP 请求。

6.3 服务间调用

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public OrderInfo selectOrderById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);

        // 通过服务名调用商品服务
        String url = "http://PRODUCT-SERVICE/product/" + orderInfo.getProductId();
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);

        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

关键点:

  • 使用服务名 PRODUCT-SERVICE 而不是 IP:端口。这是微服务调用的核心特征。
  • @LoadBalanced 会自动将服务名解析为实际的实例地址。如果有两个商品服务实例,它会自动轮换调用。

6.4 服务调用原理

为了让大家理解底层发生了什么,我们梳理一下调用链:

用户调用 -> OrderController -> OrderService
                                    │
                                    ▼
                            RestTemplate
                          (@LoadBalanced)
                                    │
                    服务名:PRODUCT-SERVICE
                                    │
                                    ▼
                            Eureka Client
                        从本地缓存获取服务列表
                                    │
                                    ▼
                            LoadBalancer
                           轮询选择实例
                                    │
                                    ▼
                            HTTP 调用 -> ProductController

原理解析:

  1. 拦截LoadBalancerClient 拦截请求。
  2. 发现:从 DiscoveryClient 获取 PRODUCT-SERVICE 的所有实例列表(来自本地缓存)。
  3. 选择:通过 ServiceInstanceChooser 选择一个实例(例如 192.168.1.5:9090)。
  4. 执行:将 URL 中的服务名替换为选中的 IP:端口,执行请求。

七、Eureka 工作原理深入

这一部分是区分“会用”和“精通”的关键。很多面试官喜欢问 Eureka 的自我保护机制和心跳原理。

7.1 服务注册流程

  1. 启动注册:Eureka Client 启动时,通过 HTTP POST 请求发送实例元数据到 Eureka Server。
  2. 信息存储:Eureka Server 将服务信息存储在双层 Map 结构中:ConcurrentHashMap<String, Map<String, Lease>>。第一层 key 是应用名,第二层 key 是实例 ID。
  3. 返回结果:Server 返回注册成功响应,建立心跳连接。
// 伪代码示意
POST /eureka/apps/{appName}
{
    "instance": {
        "instanceId": "host:service:port",
        "app": "PRODUCT-SERVICE",
        "ipAddr": "192.168.1.100",
        "port": {"$": 9090},
        "status": "UP"
    }
}

7.2 心跳机制(续约)

Eureka 采用客户端主动续约的模式。

  • 心跳间隔:默认 30 秒 (lease-renewal-interval-in-seconds)。
  • 超时剔除:超过 90 秒未收到心跳,服务被标记为 DOWN 并从注册表剔除 (lease-expiration-duration-in-seconds)。
# 客户端配置(可选)
eureka:
  instance:
    lease-renewal-interval-in-seconds: 30  # 心跳间隔
    lease-expiration-duration-in-seconds: 90  # 过期时间

生产建议:

在生产环境中,可以适当调大心跳间隔(如 60 秒),减少网络开销。但相应的,剔除时间也要调大(如 180 秒),防止网络抖动导致服务误下线。

7.3 服务列表拉取

客户端不需要每次都问服务器“谁在哪”,那样压力太大。

  • 全量拉取:启动时拉取完整服务列表。
  • 增量更新:后续只拉取变更部分(通过 delta 参数)。
  • 缓存刷新:默认 30 秒刷新一次本地缓存 (registry-fetch-interval-seconds)。
# 客户端配置(可选)
eureka:
  client:
    registry-fetch-interval-seconds: 30  # 拉取间隔

为什么要缓存?

  1. 性能:本地内存读取比网络请求快几个数量级。
  2. 容错:即使 Eureka Server 挂了,客户端依然可以利用本地缓存进行一段时间的服务调用。

八、Eureka Server 高可用集群:告别单点故障

8.1 为什么需要集群

单点 Eureka Server 存在以下风险:

  • 单点故障:Server 宕机,所有服务无法注册和发现,新服务起不来,旧服务缓存过期后无法调用。
  • 性能瓶颈:大量服务同时注册/拉取,单点性能受限,响应变慢。

8.2 集群原理

Eureka Server 集群采用 Peer to Peer (点对点) 架构,这与 ZooKeeper 的 Leader-Follower 架构不同。

  • 每个 Eureka Server 既是 Server 也是 Client。
  • Server 之间相互注册,同步服务注册表。
  • 客户端只需配置一个或多个 Server 地址。
  • 数据一致性:采用最终一致性模型。节点间同步有延迟,但保证了高可用性(AP 模型)。

8.3 搭建集群

创建两个 Eureka Server 实例,互相指向对方。

Eureka Server 1(端口 8761):

server:
  port: 8761

spring:
  application:
    name: eureka-server

eureka:
  instance:
    hostname: server1
  client:
    # 需要向其他 Server 注册
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://server2:8762/eureka/

Eureka Server 2(端口 8762):

server:
  port: 8762

spring:
  application:
    name: eureka-server

eureka:
  instance:
    hostname: server2
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://server1:8761/eureka/

注意: 集群模式下,register-with-eurekafetch-registry 必须为 true,否则节点间无法同步。

8.4 客户端配置

客户端需要知道所有集群节点的地址。

eureka:
  client:
    service-url:
      defaultZone: http://server1:8761/eureka/,http://server2:8762/eureka/

故障转移:

  • 客户端会优先使用第一个可用的 Server。
  • 如果 Server1 宕机,自动切换到 Server2。
  • 注册表在两个 Server 间同步,确保数据不丢失。

九、常见问题与解决方案:踩坑实录

在实际开发和生产环境中,我们遇到了不少问题,这里总结几条高频坑点。

9.1 服务注册失败

现象: 服务启动后,Eureka 控制台看不到该服务,日志报连接拒绝。

排查步骤:

  1. 检查网络连通ping eureka-server-ip,确保物理网络通畅。
  2. 检查配置 URLhttp://ip:port/eureka/ 格式是否正确,注意末尾的 /eureka/ 不能少。
  3. 检查防火墙:服务器端口是否开放(特别是云服务器安全组)。
  4. 查看日志:是否有 Connection refusedUnauthorized 异常信息。
  5. 版本兼容:确认 Spring Cloud 版本与 Eureka 版本是否匹配。

9.2 服务调用 404

现象: 使用服务名调用时出现 404,但直接调 IP 正常。

排查步骤:

  1. 检查服务名大小写:Eureka 中服务名是大写,调用时保持一致(虽然新版不敏感,但建议统一)。
  2. 检查 RestTemplate 是否有 @LoadBalanced:没有这个注解,服务名会被当作域名去 DNS 解析,必然失败。
  3. 检查接口路径是否正确:拼接 URL 时不要多斜杠或少斜杠。
  4. 确认服务已注册成功:去控制台看状态是不是 UP

9.3 自我保护模式

现象: Eureka 控制台出现红色警告:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP

原因:

  • 15 分钟内,心跳成功率低于 85%,触发自我保护。
  • 防止网络抖动导致服务误剔除(宁可错留,不可错杀)。

解决方案:

# 开发环境可以关闭保护模式
eureka:
  server:
    enable-self-preservation: false  # 关闭自我保护
    eviction-interval-timer-in-ms: 5000  # 缩短剔除间隔

专家建议:

生产环境不建议关闭自我保护模式。网络波动是常态,关闭后可能导致大面积服务被误剔除,引发雪崩。应该通过优化网络和心跳参数来缓解。

9.4 IP 地址显示问题

现象: 注册中心显示的是主机名(如 MacBook-Pro)而不是 IP,导致其他机器无法调用。

解决方案:
强制指定实例的 IP 地址。

eureka:
  instance:
    prefer-ip-address: true  # 优先使用 IP
    ip-address: 192.168.1.100 # 或者直接指定

十、总结与展望

核心知识点回顾

知识点

要点

记忆口诀

Eureka Server

服务注册中心,提供服务注册与发现

注册中心是大脑

Eureka Client

服务提供者/消费者,自动注册和拉取服务

客户端要会心跳

心跳机制

30 秒心跳,90 秒剔除

30 喊话,90 消失

负载均衡

RestTemplate + @LoadBalanced

注解开启负载均衡

高可用

多节点互相注册,客户端配置多个地址

互相注册保平安

Eureka 优缺点分析

优点:

  • 开源免费,Spring Cloud 集成度高,上手简单。
  • 使用简单,配置方便,开箱即用。
  • 支持高可用集群,客户端缓存机制提高了容错性。
  • 界面直观,方便查看服务状态。

缺点:

  • Netflix 已进入维护模式,不再更新新功能。
  • 性能相对于 Consul、Nacos 略低。
  • 只有 AP 模型,无法保证强一致性(但在服务发现场景下通常可接受)。

学习收获

通过本文的实战案例,你应该能够:

  1. 独立搭建 Eureka 服务注册中心。
  2. 实现服务自动注册和发现。
  3. 使用 RestTemplate 进行服务间调用。
  4. 搭建 Eureka 高可用集群。
  5. 排查常见的注册与调用问题。

 

如果觉得有帮助,请点赞、收藏、关注!有问题欢迎在评论区留言讨论。

最后送大家一句话: 微服务架构不仅仅是技术的堆砌,更是对业务边界和组织结构的重构。理解工具背后的原理,才能在设计架构时游刃有余。加油,各位未来的架构师!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐