Eureka客户端 #
一、Eureka客户端概述 #
1.1 客户端角色 #
Eureka客户端分为两种角色:
| 角色 | 说明 | 行为 |
|---|---|---|
| 服务提供者 | 提供服务的应用 | 注册服务、发送心跳 |
| 服务消费者 | 调用服务的应用 | 获取服务列表、调用服务 |
text
┌─────────────────────────────────────────────────────────────┐
│ Eureka客户端交互 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 服务提供者 服务消费者 │
│ ┌─────────┐ ┌─────────┐ │
│ │ Provider │ │ Consumer │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ │ 1. 注册服务 │ │
│ │ 2. 发送心跳 │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Eureka Server │ │
│ │ 注册中心 │ │
│ └─────────────────────────────────────────────┘ │
│ ▲ │ │
│ │ │ │
│ │ │ 3. 获取服务列表 │
│ │ │ 4. 调用服务 │
│ │ │ │
│ └──────────────────────────────┘ │
│ 5. 服务调用 │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 客户端核心功能 #
| 功能 | 说明 |
|---|---|
| 服务注册 | 向Eureka Server注册服务实例 |
| 心跳续约 | 定时发送心跳保持注册状态 |
| 服务发现 | 从Eureka Server获取服务列表 |
| 服务调用 | 通过负载均衡调用服务实例 |
| 状态更新 | 更新服务实例状态 |
二、服务提供者 #
2.1 添加依赖 #
xml
<dependencies>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
2.2 启动类 #
java
package com.example.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
2.3 配置文件 #
yaml
server:
port: 8001
spring:
application:
name: service-provider
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
register-with-eureka: true
fetch-registry: true
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: always
2.4 服务接口 #
java
package com.example.provider.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class ProviderController {
@Value("${server.port}")
private String port;
@Value("${spring.application.name}")
private String appName;
@GetMapping("/hello")
public String hello() {
return "Hello from " + appName + ", port: " + port;
}
@GetMapping("/info")
public Map<String, Object> info() {
Map<String, Object> info = new HashMap<>();
info.put("app", appName);
info.put("port", port);
info.put("timestamp", System.currentTimeMillis());
return info;
}
@PostMapping("/user")
public Map<String, Object> createUser(@RequestBody Map<String, Object> user) {
user.put("id", System.currentTimeMillis());
user.put("createdBy", appName + ":" + port);
return user;
}
}
三、服务消费者 #
3.1 添加依赖 #
xml
<dependencies>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
3.2 启动类 #
java
package com.example.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3.3 配置文件 #
yaml
server:
port: 9001
spring:
application:
name: service-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
register-with-eureka: true
fetch-registry: true
registry-fetch-interval-seconds: 30
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true
3.4 消费服务 #
java
package com.example.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
private static final String SERVICE_URL = "http://service-provider/api";
@GetMapping("/hello")
public String hello() {
return restTemplate.getForObject(SERVICE_URL + "/hello", String.class);
}
@GetMapping("/info")
public Map<String, Object> info() {
return restTemplate.getForObject(SERVICE_URL + "/info", Map.class);
}
@PostMapping("/user")
public Map<String, Object> createUser(@RequestBody Map<String, Object> user) {
return restTemplate.postForObject(SERVICE_URL + "/user", user, Map.class);
}
}
四、服务发现 #
4.1 使用DiscoveryClient #
java
package com.example.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/discovery")
public class DiscoveryController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/services")
public List<String> getServices() {
return discoveryClient.getServices();
}
@GetMapping("/instances/{serviceName}")
public List<ServiceInstance> getInstances(@PathVariable String serviceName) {
return discoveryClient.getInstances(serviceName);
}
@GetMapping("/instance/{serviceName}")
public ServiceInstance getInstance(@PathVariable String serviceName) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
if (instances != null && !instances.isEmpty()) {
return instances.get(0);
}
return null;
}
@GetMapping("/instance/{serviceName}/url")
public String getInstanceUrl(@PathVariable String serviceName) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
if (instances != null && !instances.isEmpty()) {
ServiceInstance instance = instances.get(0);
return instance.getUri().toString();
}
return null;
}
}
4.2 服务实例信息 #
java
@GetMapping("/instance/{serviceName}/detail")
public Map<String, Object> getInstanceDetail(@PathVariable String serviceName) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
if (instances != null && !instances.isEmpty()) {
ServiceInstance instance = instances.get(0);
Map<String, Object> detail = new HashMap<>();
detail.put("serviceId", instance.getServiceId());
detail.put("host", instance.getHost());
detail.put("port", instance.getPort());
detail.put("uri", instance.getUri());
detail.put("scheme", instance.getScheme());
detail.put("metadata", instance.getMetadata());
return detail;
}
return null;
}
五、健康检查 #
5.1 启用健康检查 #
yaml
eureka:
client:
healthcheck:
enabled: true
5.2 自定义健康检查 #
java
package com.example.provider.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
boolean healthy = checkHealth();
if (healthy) {
return Health.up()
.withDetail("service", "provider")
.withDetail("status", "running")
.build();
} else {
return Health.down()
.withDetail("service", "provider")
.withDetail("status", "error")
.build();
}
}
private boolean checkHealth() {
return true;
}
}
5.3 服务状态变更 #
java
package com.example.provider.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/status")
public class StatusController {
@Autowired
private EurekaInstanceConfigBean eurekaInstanceConfig;
@PutMapping("/down")
public String setDown() {
eurekaInstanceConfig.setInitialStatus(InstanceInfo.InstanceStatus.DOWN);
return "Status set to DOWN";
}
@PutMapping("/up")
public String setUp() {
eurekaInstanceConfig.setInitialStatus(InstanceInfo.InstanceStatus.UP);
return "Status set to UP";
}
@PutMapping("/out-of-service")
public String setOutOfService() {
eurekaInstanceConfig.setInitialStatus(InstanceInfo.InstanceStatus.OUT_OF_SERVICE);
return "Status set to OUT_OF_SERVICE";
}
}
六、配置详解 #
6.1 客户端配置 #
yaml
eureka:
client:
enabled: true
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka/
registry-fetch-interval-seconds: 30
instance-info-replication-interval-seconds: 30
initial-instance-info-replication-interval-seconds: 40
eureka-service-url-poll-interval-seconds: 300
eureka-server-read-timeout-seconds: 8
eureka-server-connect-timeout-seconds: 5
eureka-server-total-connections: 200
eureka-server-total-connections-per-host: 50
eureka-connection-idle-timeout-seconds: 30
healthcheck:
enabled: true
6.2 实例配置 #
yaml
eureka:
instance:
hostname: ${HOSTNAME:localhost}
appname: ${spring.application.name}
instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
prefer-ip-address: true
ip-address: ${IP_ADDRESS:127.0.0.1}
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90
status-page-url-path: /actuator/info
health-check-url-path: /actuator/health
home-page-url-path: /
metadata-map:
version: 1.0.0
zone: default
6.3 配置项说明 #
| 配置项 | 默认值 | 说明 |
|---|---|---|
| register-with-eureka | true | 是否注册到Eureka |
| fetch-registry | true | 是否拉取注册表 |
| registry-fetch-interval-seconds | 30 | 拉取注册表间隔 |
| lease-renewal-interval-in-seconds | 30 | 心跳间隔 |
| lease-expiration-duration-in-seconds | 90 | 过期时间 |
| prefer-ip-address | false | 是否优先使用IP |
七、多实例部署 #
7.1 配置多实例 #
yaml
spring:
application:
name: service-provider
server:
port: ${PORT:8001}
eureka:
client:
service-url:
defaultZone: ${EUREKA_SERVER_URL:http://localhost:8761/eureka/}
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true
7.2 启动多实例 #
bash
# 实例1
java -jar provider.jar --server.port=8001
# 实例2
java -jar provider.jar --server.port=8002
# 实例3
java -jar provider.jar --server.port=8003
7.3 Docker部署 #
dockerfile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/provider.jar app.jar
ENV PORT=8001
ENV EUREKA_SERVER_URL=http://eureka:8761/eureka/
EXPOSE ${PORT}
ENTRYPOINT ["java", "-jar", "app.jar"]
yaml
version: '3'
services:
provider-1:
build: .
environment:
- PORT=8001
- EUREKA_SERVER_URL=http://eureka:8761/eureka/
ports:
- "8001:8001"
depends_on:
- eureka
provider-2:
build: .
environment:
- PORT=8002
- EUREKA_SERVER_URL=http://eureka:8761/eureka/
ports:
- "8002:8002"
depends_on:
- eureka
provider-3:
build: .
environment:
- PORT=8003
- EUREKA_SERVER_URL=http://eureka:8761/eureka/
ports:
- "8003:8003"
depends_on:
- eureka
八、元数据 #
8.1 配置元数据 #
yaml
eureka:
instance:
metadata-map:
version: 1.0.0
zone: zone-1
weight: 100
owner: team-a
8.2 使用元数据 #
java
@GetMapping("/instance/{serviceName}/metadata")
public Map<String, String> getInstanceMetadata(@PathVariable String serviceName) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
if (instances != null && !instances.isEmpty()) {
ServiceInstance instance = instances.get(0);
return instance.getMetadata();
}
return null;
}
8.3 基于元数据的路由 #
java
@GetMapping("/instance/{serviceName}/zone/{zone}")
public ServiceInstance getInstanceByZone(
@PathVariable String serviceName,
@PathVariable String zone) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
return instances.stream()
.filter(instance -> zone.equals(instance.getMetadata().get("zone")))
.findFirst()
.orElse(null);
}
九、最佳实践 #
9.1 生产环境配置 #
yaml
spring:
application:
name: service-provider
server:
port: ${PORT:8001}
eureka:
client:
service-url:
defaultZone: ${EUREKA_SERVER_URL:http://eureka1:8761/eureka/,http://eureka2:8762/eureka/}
register-with-eureka: true
fetch-registry: true
registry-fetch-interval-seconds: 30
healthcheck:
enabled: true
instance:
hostname: ${HOSTNAME:localhost}
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true
ip-address: ${IP_ADDRESS:127.0.0.1}
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90
metadata-map:
version: @project.version@
zone: ${ZONE:default}
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: when-authorized
9.2 优雅停机 #
java
@Configuration
public class GracefulShutdownConfig {
@PreDestroy
public void onShutdown() {
System.out.println("Graceful shutdown initiated...");
}
}
yaml
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
9.3 注意事项 #
| 注意点 | 说明 |
|---|---|
| 心跳间隔 | 生产环境建议30秒 |
| 过期时间 | 建议为心跳间隔的3倍 |
| 服务名 | 使用小写字母和连字符 |
| 元数据 | 合理使用元数据进行路由 |
十、总结 #
10.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 服务注册 | @EnableDiscoveryClient启用服务发现 |
| 心跳续约 | 定时发送心跳保持注册状态 |
| 服务发现 | DiscoveryClient获取服务实例 |
| 负载均衡 | @LoadBalanced实现客户端负载均衡 |
10.2 下一步 #
现在你已经掌握了Eureka客户端的使用,接下来让我们学习 Nacos注册中心,了解阿里巴巴的服务发现方案!
最后更新:2026-03-28