在上一篇中,我们理解了微服务架构的核心理念以及Spring Cloud为我们提供的强大工具集。我们提到,微服务架构的一个核心挑战在于,服务实例的网络位置是动态的,服务之间需要一种机制来互相定位。
想象一下,你开了一家新店(一个微服务实例),你希望顾客(其他服务或客户端)能找到你。你会怎么做?
注册: 你会去工商局(服务注册中心)登记你的店名、地址、联系方式等信息。
发现: 顾客想找你这家店时,会去工商局查询你的信息,然后根据地址找过来。
健康检查/心跳: 你需要定期向工商局报告你还在正常营业(发送心跳),如果长时间不报告,工商局可能会认为你的店关门了,就不会再把你的信息给顾客。
Spring Cloud Netflix Eureka (简称 Eureka) 就是这样一个“工商局”。它包含两个核心组件:
Eureka Server (服务注册中心): 提供服务注册和发现的功能。所有服务提供者(Eureka Client)启动时向Eureka Server注册自己的信息(服务名、IP、端口、健康状况等),并定期发送心跳来维持注册状态。Eureka Server会维护一个所有已注册服务实例的清单。
Eureka Client (服务提供者/消费者):
作为服务提供者,它会向Eureka Server注册自己。
作为服务消费者,它会从Eureka Server拉取注册表信息,缓存到本地,并根据服务名(通常结合客户端负载均衡器如Spring Cloud LoadBalancer)找到目标服务的实例地址进行调用。
Eureka的核心优势在于其AP(可用性优先)设计哲学 (基于CAP理论)。 即使部分Eureka Server节点失效,只要客户端缓存了注册表信息,仍然可以进行服务调用。Eureka Server之间也会互相复制注册信息,实现高可用。
读完本文,你将能够:
搭建一个独立的Eureka Server。
将一个Spring Boot应用配置为Eureka Client,并将其注册到Eureka Server。
理解服务注册、服务发现和心跳机制的基本工作流程。
通过Eureka Server的Web界面查看已注册的服务。
准备好为你的微服务们建立一个可靠的“导航系统”了吗?
一、搭建Eureka Server (服务注册中心)
Eureka Server本身也是一个Spring Boot应用。
1. 创建Spring Boot项目 (Eureka Server):
使用Spring Initializr (start.spring.io) 或你的IDE创建一个新的Spring Boot项目,包含以下依赖:
Spring Web: Eureka Server提供Web界面和REST API。
Eureka Server: 核心依赖,用于构建Eureka注册中心。
Maven依赖 (pom.xml):
<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-server</artifactId>
</dependency>
<!-- Spring Cloud BOM (Bill of Materials) for version management -->
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version> <!-- 例如: 2022.0.4 -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注意: 务必在<dependencyManagement>中引入spring-cloud-dependencies BOM来统一管理Spring Cloud各个组件的版本。spring-cloud.version需要与你的Spring Boot版本兼容(参考Spring Cloud官网的版本对应关系)。
2. 启用Eureka Server (@EnableEurekaServer):
在主启动类上添加@EnableEurekaServer注解。
package com.example.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; // 导入
@SpringBootApplication
@EnableEurekaServer // 标记这个应用是一个Eureka Server
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
3. 配置Eureka Server (application.yml 或 application.properties):
# application.yml for Eureka Server
server:
port: 8761 # Eureka Server的默认端口
spring:
application:
name: eureka-server # 应用名称, 会显示在Eureka管理界面
eureka:
instance:
hostname: localhost # Eureka实例的主机名 (单机配置)
# (可选) instance-id: ${spring.application.name}:${server.port}
client:
# 对于单机Eureka Server, 这两项通常设为false, 因为它自己就是注册中心, 不需要注册自己或从其他地方获取注册信息
register-with-eureka: false # 是否将自己注册到Eureka Server (自己就是server, 所以是false)
fetch-registry: false # 是否从Eureka Server获取注册信息 (自己就是server, 所以是false)
service-url:
# 对于单机模式, 这个地址指向自己 (通常不需要, 因为上面两项为false)
# 但如果要搭建集群, 这里会配置其他Eureka Server节点的地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# (可选) 关闭自我保护模式 (开发时方便快速看到服务下线, 生产环境不建议关闭)
# server:
# enable-self-preservation: false
# eviction-interval-timer-in-ms: 5000 # 清理无效节点的时间间隔 (毫秒)
核心配置解释:
server.port: Eureka Server运行的端口,约定俗成为8761。
spring.application.name: 应用名,会显示在Eureka的管理界面。
eureka.client.register-with-eureka: false: 因为这个应用本身就是Eureka Server,所以它不需要向自己注册。
eureka.client.fetch-registry: false: 同理,它不需要从自己这里获取注册表。
eureka.client.service-url.defaultZone: 指定Eureka Server的地址。对于单机模式,这个配置虽然写了,但因为上面两项为false,实际作用不大。在Eureka Client端配置这个地址才至关重要。 当搭建Eureka Server集群时,这里会配置其他Peer节点的地址,用于节点间同步注册信息。
eureka.server.enable-self-preservation: Eureka的自我保护模式。在网络分区等情况下,如果Eureka Server在短时间内丢失了大量服务实例的心跳,它会进入自我保护模式,不再剔除这些“暂时失联”的实例,以防止因网络问题导致大量可用服务被错误移除。开发时为了快速看到服务下线效果,有时会临时关闭它,但生产环境通常应保持开启。
4. 运行Eureka Server:
启动这个Spring Boot应用。如果一切顺利,你可以在浏览器中访问 http://localhost:8761/,看到Eureka的Web管理界面。此时,界面上应该还没有任何服务实例注册。
二、创建服务提供者 (Eureka Client)
现在我们来创建一个简单的Spring Boot应用,并让它作为服务提供者注册到刚才搭建的Eureka Server上。
1. 创建Spring Boot项目 (例如 user-service):
包含以下依赖:
Spring Web: 提供REST API。
Eureka Discovery Client: 核心依赖,用于将应用注册为Eureka客户端。
(可选) Spring Boot Actuator: 用于健康检查。
Maven依赖 (pom.xml - user-service):
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <!-- 用于健康检查 -->
</dependency>
<!-- Spring Cloud BOM (确保与Eureka Server项目使用相同的BOM版本) -->
</dependencies>
<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>
2. 启用Eureka Client (@EnableDiscoveryClient 或 @EnableEurekaClient):
在主启动类上添加注解。@EnableDiscoveryClient是Spring Cloud提供的更通用的服务发现注解(可用于Eureka, Consul, Nacos等),而@EnableEurekaClient是专门针对Eureka的。推荐使用@EnableDiscoveryClient。
package com.example.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; // 推荐使用
@SpringBootApplication
@EnableDiscoveryClient // 标记这个应用是一个服务发现客户端 (可以注册和发现服务)
// 或者 @EnableEurekaClient // 仅适用于Eureka
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
3. 配置Eureka Client (application.yml - user-service):
# application.yml for user-service
server:
port: 8081 # 服务提供者运行的端口 (确保与Eureka Server端口不同)
spring:
application:
name: user-service # 服务名称, 这是注册到Eureka Server上的关键标识!
# 服务消费者会通过这个名称来查找服务实例
eureka:
client:
# register-with-eureka: true # (默认即为true) 是否将自己注册到Eureka Server
# fetch-registry: true # (默认即为true) 是否从Eureka Server获取注册信息
service-url:
defaultZone: http://localhost:8761/eureka/ # 指定Eureka Server的地址!
instance:
# (可选) 自定义实例ID, 默认为 ${spring.application.name}:${server.port}
# instance-id: ${spring.application.name}:${random.value} # 可以使用随机值避免端口冲突时的ID冲突
hostname: localhost # (可选) 如果需要让Eureka Server显示特定的主机名
# (可选) 配置健康检查URL, Actuator的/actuator/health端点会自动被Eureka使用
# health-check-url-path: /actuator/health
# status-page-url-path: /actuator/info
# (可选) 配置心跳间隔 (默认30秒)
# lease-renewal-interval-in-seconds: 10
# (可选) 配置服务过期时间 (默认90秒, 即如果90秒内没收到心跳, 实例被剔除)
# lease-expiration-duration-in-seconds: 30
核心配置解释:
server.port: 这个服务提供者运行的端口,确保与Eureka Server的端口不同。
spring.application.name: 极其重要! 这是服务注册到Eureka Server时使用的服务名 (Service ID)。其他服务将来会通过这个服务名来发现和调用它。
eureka.client.service-url.defaultZone: 必须正确配置! 指向你的Eureka Server的地址。
eureka.instance.*: 可以配置实例相关的元数据,如实例ID、主机名、健康检查路径等。如果引入了spring-boot-starter-actuator,Eureka Client会自动使用/actuator/health端点作为健康检查URL。
4. (可选)创建一个简单的REST Controller (user-service):
package com.example.userservice.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class UserController {
@Value("${server.port}") // 注入当前服务端口, 方便演示
private String serverPort;
@GetMapping("/{id}")
public String getUserById(@PathVariable Long id) {
return "User details for ID " + id + " (from port: " + serverPort + ")";
}
}
5. 运行服务提供者 (user-service):
启动这个user-service应用。稍等片刻(默认心跳间隔30秒,注册可能需要一点时间),然后刷新Eureka Server的管理界面 (http://localhost:8761/)。
你应该能在 "Instances currently registered with Eureka" 部分看到名为 USER-SERVICE (通常是大写) 的服务,以及它的实例信息(IP、端口、状态等)。状态初始可能是STARTING,之后会变为UP(如果健康检查通过)。
三、理解核心机制
服务注册 (Registration): Eureka Client(如user-service)启动后,会向eureka.client.service-url.defaultZone配置的Eureka Server地址发送注册请求,包含自己的服务名、IP、端口、健康状况等信息。
服务续约/心跳 (Renewal/Heartbeat): Client会定期(默认30秒)向Server发送心跳,告知Server自己仍然存活。如果Server在一定时间内(默认90秒)没有收到某个实例的心跳,会认为该实例已下线,并将其从注册表中剔除(除非开启了自我保护模式且触发了保护条件)。
服务发现 (Discovery): 其他Client(服务消费者)启动时或定期(默认30秒)从Eureka Server拉取最新的服务注册表信息,并缓存到本地。当需要调用某个服务时,它会从本地缓存的注册表中根据服务名查找可用的实例列表。
服务下线 (Cancellation): Client在正常关闭时,会向Server发送一个取消注册的请求。
四、高可用Eureka Server集群 (简述)
在生产环境中,单个Eureka Server节点是单点故障。通常会搭建Eureka Server集群来实现高可用。
配置集群的关键在于让每个Eureka Server节点知道其他节点的存在,并通过eureka.client.service-url.defaultZone互相指定对方的地址(但仍然将register-with-eureka和fetch-registry设为true,因为它们既是Server也是Client,需要同步注册信息)。
例如,两个节点的集群配置 (application-peer1.yml):
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: peer1 # 或真实IP/域名
client:
register-with-eureka: true # 注册到其他peer
fetch-registry: true # 从其他peer获取信息
service-url:
defaultZone: http://peer2:8762/eureka/ # 指向另一个节点
application-peer2.yml 对应配置 peer1:8761。
客户端的defaultZone则配置所有Eureka Server节点的地址,用逗号分隔。
五、总结:迈出微服务的第一步
通过搭建Eureka Server和配置Eureka Client,我们成功地让一个微服务实例(user-service)注册到了服务注册中心,并且可以通过Eureka的Web界面查看到它的状态。这为后续的服务间调用打下了坚实的基础。
Spring Cloud Netflix Eureka提供了一种相对简单且经过广泛验证的服务注册与发现解决方案。虽然Netflix已将其置于维护模式,但它在许多现有系统中仍被大量使用,并且其AP设计理念对于理解服务发现非常重要。
在后续的文章中,我们将看到服务消费者如何利用从Eureka获取到的信息,结合客户端负载均衡器来调用这个user-service。我们也会在适当的时候介绍更新的服务发现组件,如Nacos或Consul。