目录
- Eureka概述
- 核心概念
- 架构设计
- 环境搭建
- Eureka Server搭建
- Eureka Client集成
- 服务注册与发现
- 高可用配置
- 安全配置
- 监控管理
- 性能优化
- 故障排查
- 实战案例
- 最佳实践
Eureka概述
Netflix Eureka是Spring Cloud Netflix套件中的服务注册与发现组件,采用AP(可用性和分区容错性)设计原则。虽然Netflix已停止对Eureka的维护,但在Spring Cloud生态中仍被广泛使用。
核心特性
服务注册:服务实例自动注册到注册中心
服务发现:客户端从注册中心获取服务列表
健康检查:定期检查服务实例健康状态
负载均衡:与Ribbon集成提供客户端负载均衡
故障转移:服务不可用时自动剔除
缓存机制:客户端缓存服务列表,提高性能
自我保护:网络分区时保护已注册的服务
应用场景
微服务A → Eureka Server ← 微服务B
↓ ↑
服务注册 服务发现
微服务架构:大量微服务的注册与发现
动态伸缩:服务实例动态增减
负载均衡:分布式负载均衡
故障隔离:快速发现和隔离故障服务
核心概念
服务注册中心(Registry)
Eureka Server作为服务注册中心,维护所有可用服务实例的信息。
// 服务注册表结构
public class InstanceInfo {
private String instanceId; // 实例ID
private String appName; // 应用名称
private String hostName; // 主机名
private String ipAddr; // IP地址
private int port; // 端口
private InstanceStatus status; // 实例状态
private String vipAddress; // VIP地址
private String secureVipAddress; // 安全VIP地址
private LeaseInfo leaseInfo; // 租约信息
private DataCenterInfo dataCenterInfo; // 数据中心信息
}
服务提供者(Service Provider)
向Eureka Server注册服务并定期发送心跳:
// 服务实例
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
服务消费者(Service Consumer)
从Eureka Server获取服务列表并调用服务:
// 服务调用
@RestController
public class UserController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/services")
public List<ServiceInstance> getServices() {
return discoveryClient.getInstances("user-service");
}
}
心跳机制(Heartbeat)
服务实例定期向Eureka Server发送心跳证明存活:
eureka:
instance:
lease-renewal-interval-in-seconds: 30 # 心跳间隔
lease-expiration-duration-in-seconds: 90 # 租约过期时间
自我保护机制(Self Preservation)
当心跳失败比例超过阈值时,Eureka进入自我保护模式:
eureka:
server:
enable-self-preservation: true # 启用自我保护
renewal-percent-threshold: 0.85 # 续约百分比阈值
架构设计
整体架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Eureka Server │ │ Eureka Server │ │ Eureka Server │
│ (Primary) │◄──►│ (Replica 1) │◄──►│ (Replica 2) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
▲ ▲ ▲
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Service A │ │ Service B │ │ Service C │
│ (Provider) │ │ (Consumer) │ │ (Provider) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
核心组件
Registry(注册表)
存储所有服务实例信息
提供注册、续约、下线接口
定期清理过期实例
Client(客户端)
注册服务实例到Registry
定期发送心跳续约
缓存服务列表本地
Server(服务端)
接收服务注册请求
管理服务实例生命周期
集群间数据同步
环境搭建
依赖管理
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.8</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>
</project>
版本兼容性
Eureka Server搭建
单机模式
依赖配置
<dependencies>
<!-- Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- Web Starter -->
<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>
<!-- Security (可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
主启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
配置文件
# application.yml
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
prefer-ip-address: false
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
# 期望心跳阈值
renewal-percent-threshold: 0.85
# 响应缓存更新间隔
response-cache-update-interval-ms: 30000
# 响应缓存过期时间
response-cache-auto-expiration-in-seconds: 180
# 监控配置
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
# 日志配置
logging:
level:
com.netflix.eureka: DEBUG
com.netflix.discovery: DEBUG
集群模式
多节点配置
# eureka-server-1.yml
server:
port: 8761
spring:
application:
name: eureka-server
profiles:
active: peer1
eureka:
instance:
hostname: eureka-server-1
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka-server-2:8762/eureka/,http://eureka-server-3:8763/eureka/
server:
enable-self-preservation: true
renewal-percent-threshold: 0.85
---
# eureka-server-2.yml
server:
port: 8762
spring:
application:
name: eureka-server
profiles:
active: peer2
eureka:
instance:
hostname: eureka-server-2
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-3:8763/eureka/
---
# eureka-server-3.yml
server:
port: 8763
spring:
application:
name: eureka-server
profiles:
active: peer3
eureka:
instance:
hostname: eureka-server-3
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka-server-1:8761/eureka/,http://eureka-server-2:8762/eureka/
Docker Compose部署
# docker-compose.yml
version: '3.8'
services:
eureka-server-1:
image: eureka-server:latest
hostname: eureka-server-1
ports:
- "8761:8761"
environment:
- SPRING_PROFILES_ACTIVE=peer1
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server-2:8762/eureka/,http://eureka-server-3:8763/eureka/
networks:
- eureka-network
eureka-server-2:
image: eureka-server:latest
hostname: eureka-server-2
ports:
- "8762:8762"
environment:
- SPRING_PROFILES_ACTIVE=peer2
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server-1:8761/eureka/,http://eureka-server-3:8763/eureka/
networks:
- eureka-network
eureka-server-3:
image: eureka-server:latest
hostname: eureka-server-3
ports:
- "8763:8763"
environment:
- SPRING_PROFILES_ACTIVE=peer3
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server-1:8761/eureka/,http://eureka-server-2:8762/eureka/
networks:
- eureka-network
networks:
eureka-network:
driver: bridge
Eureka Client集成
服务提供者
依赖配置
<dependencies>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Web Starter -->
<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>
主启动类
@SpringBootApplication
@EnableEurekaClient // 或使用 @EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
配置文件
# application.yml
server:
port: 8081
spring:
application:
name: user-service
eureka:
instance:
# 实例ID(唯一标识)
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
# 使用IP地址注册
prefer-ip-address: true
# IP地址(自动获取)
ip-address: ${spring.cloud.client.ip-address}
# 心跳间隔
lease-renewal-interval-in-seconds: 30
# 租约过期时间
lease-expiration-duration-in-seconds: 90
# 健康检查路径
health-check-url-path: /actuator/health
# 状态页面路径
status-page-url-path: /actuator/info
# 元数据
metadata-map:
zone: zone1
version: v1.0
author: developer
client:
# 注册到Eureka
register-with-eureka: true
# 获取注册表
fetch-registry: true
# Eureka Server地址
service-url:
defaultZone: http://localhost:8761/eureka/
# 获取服务列表间隔
registry-fetch-interval-seconds: 30
# 是否可用区优先
prefer-same-zone-eureka: true
# 健康检查配置
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
health:
eureka:
enabled: true
# 日志配置
logging:
level:
com.netflix.eureka: DEBUG
com.netflix.discovery: DEBUG
服务控制器
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
@Autowired
private DiscoveryClient discoveryClient;
@Value("${server.port}")
private String port;
@GetMapping("/info")
public Map<String, Object> getServiceInfo() {
Map<String, Object> info = new HashMap<>();
info.put("service", "user-service");
info.put("port", port);
info.put("timestamp", System.currentTimeMillis());
return info;
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
log.info("获取用户信息: id={}, port={}", id, port);
User user = new User();
user.setId(id);
user.setName("User " + id);
user.setPort(port);
return user;
}
@GetMapping("/services")
public List<String> getServices() {
return discoveryClient.getServices();
}
@GetMapping("/instances/{serviceId}")
public List<ServiceInstance> getInstances(@PathVariable String serviceId) {
return discoveryClient.getInstances(serviceId);
}
}
@Data
public class User {
private Long id;
private String name;
private String port;
}
服务消费者
RestTemplate方式
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
@Slf4j
public class UserService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
public User getUserById(Long id) {
// 方式1:使用服务名调用(推荐)
String url = "http://user-service/api/users/" + id;
return restTemplate.getForObject(url, User.class);
}
public User getUserByIdWithDiscovery(Long id) {
// 方式2:手动服务发现
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
if (instances.isEmpty()) {
throw new RuntimeException("No available instances for user-service");
}
// 简单轮询负载均衡
ServiceInstance instance = instances.get((int) (System.currentTimeMillis() % instances.size()));
String url = String.format("http://%s:%d/api/users/%d",
instance.getHost(), instance.getPort(), id);
return restTemplate.getForObject(url, User.class);
}
public List<ServiceInstance> getAvailableInstances() {
return discoveryClient.getInstances("user-service");
}
}
WebClient方式
@Configuration
public class WebClientConfig {
@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
@Service
@Slf4j
public class UserWebService {
@Autowired
private WebClient.Builder webClientBuilder;
public Mono<User> getUserById(Long id) {
return webClientBuilder.build()
.get()
.uri("http://user-service/api/users/{id}", id)
.retrieve()
.bodyToMono(User.class)
.doOnNext(user -> log.info("获取用户: {}", user))
.doOnError(error -> log.error("获取用户失败", error));
}
public Flux<User> getAllUsers() {
return webClientBuilder.build()
.get()
.uri("http://user-service/api/users")
.retrieve()
.bodyToFlux(User.class);
}
}
服务注册与发现
注册流程
@Component
@Slf4j
public class ServiceRegistrationListener {
@EventListener
public void handleInstanceRegistered(InstanceRegisteredEvent<?> event) {
log.info("服务实例已注册: {}", event.getInstanceInfo());
}
@EventListener
public void handleInstanceCanceled(EurekaInstanceCanceledEvent event) {
log.info("服务实例已注销: appName={}, serverId={}",
event.getAppName(), event.getServerId());
}
@EventListener
public void handleInstanceRenewed(EurekaInstanceRenewedEvent event) {
log.debug("服务实例续约: appName={}, serverId={}",
event.getAppName(), event.getServerId());
}
}
自定义注册信息
@Component
public class CustomInstanceInfoContributor implements HealthIndicator {
@Override
public Health health() {
return Health.up()
.withDetail("service", "user-service")
.withDetail("version", "1.0.0")
.withDetail("description", "用户管理服务")
.build();
}
}
@Configuration
public class EurekaInstanceConfig {
@Bean
@Primary
public EurekaInstanceConfigBean eurekaInstanceConfig() {
EurekaInstanceConfigBean config = new EurekaInstanceConfigBean();
// 自定义实例ID
config.setInstanceId(generateInstanceId());
// 自定义元数据
Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0.0");
metadata.put("environment", "production");
metadata.put("team", "backend-team");
config.setMetadataMap(metadata);
return config;
}
private String generateInstanceId() {
try {
String hostName = InetAddress.getLocalHost().getHostName();
String port = environment.getProperty("server.port", "8080");
return String.format("%s:%s:%s",
environment.getProperty("spring.application.name"), hostName, port);
} catch (Exception e) {
return UUID.randomUUID().toString();
}
}
}
服务发现事件监听
@Component
@Slf4j
public class ServiceDiscoveryListener {
@EventListener
public void handleHeartbeatEvent(HeartbeatEvent event) {
log.debug("心跳事件: {}", event.getValue());
}
@EventListener
public void handleRefreshEvent(RefreshRemoteApplicationEvent event) {
log.info("远程刷新事件: originService={}, destinationService={}",
event.getOriginService(), event.getDestinationService());
}
@EventListener
public void handleStatusChange(StatusChangeEvent event) {
log.info("服务状态变更: status={} -> {}",
event.getPreviousStatus(), event.getStatus());
}
}
自定义负载均衡策略
@Configuration
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
// 可选择不同的负载均衡策略
return new WeightedResponseTimeRule(); // 响应时间加权
// return new RandomRule(); // 随机
// return new RoundRobinRule(); // 轮询
// return new AvailabilityFilteringRule(); // 可用性过滤
}
@Bean
public IPing ribbonPing() {
return new PingUrl(); // HTTP ping
}
@Bean
public ServerListFilter<Server> ribbonServerListFilter() {
return new ZonePreferenceServerListFilter(); // 区域优先
}
}
// 自定义负载均衡规则
public class CustomRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return null;
}
List<Server> servers = lb.getReachableServers();
if (servers.isEmpty()) {
return null;
}
// 自定义选择逻辑:优先选择元数据中version为v1.0的实例
for (Server server : servers) {
if (server instanceof DiscoveryEnabledServer) {
DiscoveryEnabledServer discoveryServer = (DiscoveryEnabledServer) server;
InstanceInfo instanceInfo = discoveryServer.getInstanceInfo();
String version = instanceInfo.getMetadata().get("version");
if ("v1.0".equals(version)) {
return server;
}
}
}
// 如果没有v1.0版本,使用轮询策略
return servers.get((int) (System.currentTimeMillis() % servers.size()));
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 初始化配置
}
}
高可用配置
多数据中心部署
# 数据中心1配置
eureka:
instance:
data-center-info:
name: MyOwn
metadata:
datacenter: dc1
zone: zone1a
client:
prefer-same-zone-eureka: true
service-url:
zone1a: http://eureka-dc1-1:8761/eureka/,http://eureka-dc1-2:8762/eureka/
zone1b: http://eureka-dc1-3:8763/eureka/,http://eureka-dc1-4:8764/eureka/
zone2a: http://eureka-dc2-1:8761/eureka/,http://eureka-dc2-2:8762/eureka/
availability-zones:
dc1: zone1a,zone1b
dc2: zone2a
region: dc1
---
# 数据中心2配置
eureka:
instance:
data-center-info:
name: MyOwn
metadata:
datacenter: dc2
zone: zone2a
client:
prefer-same-zone-eureka: true
service-url:
zone2a: http://eureka-dc2-1:8761/eureka/,http://eureka-dc2-2:8762/eureka/
zone1a: http://eureka-dc1-1:8761/eureka/,http://eureka-dc1-2:8762/eureka/
availability-zones:
dc2: zone2a
dc1: zone1a,zone1b
region: dc2
故障转移配置
@Configuration
public class EurekaClientConfig {
@Bean
public DiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
DiscoveryClientOptionalArgs args = new DiscoveryClientOptionalArgs();
// 自定义连接超时
args.setConnectTimeoutMs(5000);
args.setReadTimeoutMs(10000);
// 自定义重试策略
args.setRetryConnectionsToEureka(true);
args.setConnectionRetriesCount(3);
return args;
}
@Bean
public EurekaClientConfigBean eurekaClientConfigBean() {
EurekaClientConfigBean config = new EurekaClientConfigBean();
// 故障转移配置
config.setUseDnsForFetchingServiceUrls(false);
config.setPreferSameZoneEureka(true);
config.setFilterOnlyUpInstances(true);
// 缓存配置
config.setCacheRefreshExecutorThreadPoolSize(2);
config.setHeartbeatExecutorThreadPoolSize(2);
return config;
}
}
网络分区处理
@Component
@Slf4j
public class NetworkPartitionHandler {
@Autowired
private EurekaClient eurekaClient;
@Scheduled(fixedRate = 30000) // 30秒检查一次
public void checkNetworkPartition() {
try {
Applications applications = eurekaClient.getApplications();
if (applications == null || applications.getRegisteredApplications().isEmpty()) {
log.warn("检测到网络分区,本地缓存为空");
handleNetworkPartition();
}
} catch (Exception e) {
log.error("网络分区检查失败", e);
handleNetworkPartition();
}
}
private void handleNetworkPartition() {
// 启用本地缓存
// 降级到本地服务发现
// 记录告警日志
log.error("网络分区处理:启用降级模式");
}
}
安全配置
HTTP Basic认证
# Eureka Server安全配置
spring:
security:
user:
name: eureka
password: ${EUREKA_PASSWORD:eureka123}
roles: ADMIN
eureka:
server:
# 禁用CSRF(Eureka客户端不支持)
csrf:
enabled: false
@Configuration
@EnableWebSecurity
public class EurekaServerSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/**").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
# Eureka Client认证配置
eureka:
client:
service-url:
defaultZone: http://eureka:eureka123@localhost:8761/eureka/
HTTPS配置
# HTTPS配置
server:
port: 8443
ssl:
enabled: true
key-store: classpath:eureka-server.p12
key-store-password: changeit
key-store-type: PKCS12
key-alias: eureka-server
eureka:
instance:
secure-port: ${server.port}
secure-port-enabled: true
non-secure-port-enabled: false
home-page-url: https://${eureka.instance.hostname}:${server.port}/
status-page-url: https://${eureka.instance.hostname}:${server.port}/actuator/info
health-check-url: https://${eureka.instance.hostname}:${server.port}/actuator/health
client:
service-url:
defaultZone: https://eureka:eureka123@localhost:8443/eureka/
OAuth2集成
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/auth/realms/eureka
@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/eureka/**").hasAuthority("SCOPE_eureka.read")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt());
return http.build();
}
}
监控管理
自定义健康检查
@Component
public class EurekaHealthIndicator implements HealthIndicator {
@Autowired
private EurekaClient eurekaClient;
@Override
public Health health() {
try {
Applications applications = eurekaClient.getApplications();
int instanceCount = applications.getRegisteredApplications().stream()
.mapToInt(app -> app.getInstances().size())
.sum();
if (instanceCount > 0) {
return Health.up()
.withDetail("instances", instanceCount)
.withDetail("applications", applications.getRegisteredApplications().size())
.withDetail("status", "Eureka is up and running")
.build();
} else {
return Health.down()
.withDetail("status", "No instances registered")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}
Micrometer集成
@Component
public class EurekaMetrics {
private final MeterRegistry meterRegistry;
private final EurekaClient eurekaClient;
public EurekaMetrics(MeterRegistry meterRegistry, EurekaClient eurekaClient) {
this.meterRegistry = meterRegistry;
this.eurekaClient = eurekaClient;
// 注册指标
Gauge.builder("eureka.registered.instances")
.description("Number of registered instances")
.register(meterRegistry, this, EurekaMetrics::getRegisteredInstanceCount);
Gauge.builder("eureka.available.instances")
.description("Number of available instances")
.register(meterRegistry, this, EurekaMetrics::getAvailableInstanceCount);
}
private double getRegisteredInstanceCount() {
try {
Applications applications = eurekaClient.getApplications();
return applications.getRegisteredApplications().stream()
.mapToInt(app -> app.getInstances().size())
.sum();
} catch (Exception e) {
return 0;
}
}
private double getAvailableInstanceCount() {
try {
Applications applications = eurekaClient.getApplications();
return applications.getRegisteredApplications().stream()
.flatMap(app -> app.getInstances().stream())
.filter(instance -> instance.getStatus() == InstanceInfo.InstanceStatus.UP)
.count();
} catch (Exception e) {
return 0;
}
}
}
Dashboard监控
@RestController
@RequestMapping("/dashboard")
public class EurekaDashboardController {
@Autowired
private EurekaClient eurekaClient;
@GetMapping("/services")
public Map<String, Object> getServicesInfo() {
Map<String, Object> info = new HashMap<>();
Applications applications = eurekaClient.getApplications();
List<Map<String, Object>> services = new ArrayList<>();
for (Application app : applications.getRegisteredApplications()) {
Map<String, Object> serviceInfo = new HashMap<>();
serviceInfo.put("name", app.getName());
serviceInfo.put("instanceCount", app.getInstances().size());
serviceInfo.put("upInstanceCount", app.getInstancesAsIsFromEureka().stream()
.filter(instance -> instance.getStatus() == InstanceInfo.InstanceStatus.UP)
.count());
List<Map<String, Object>> instances = new ArrayList<>();
for (InstanceInfo instance : app.getInstances()) {
Map<String, Object> instanceInfo = new HashMap<>();
instanceInfo.put("instanceId", instance.getInstanceId());
instanceInfo.put("ipAddr", instance.getIPAddr());
instanceInfo.put("port", instance.getPort());
instanceInfo.put("status", instance.getStatus());
instanceInfo.put("lastUpdatedTimestamp", instance.getLastUpdatedTimestamp());
instances.add(instanceInfo);
}
serviceInfo.put("instances", instances);
services.add(serviceInfo);
}
info.put("services", services);
info.put("totalServices", applications.getRegisteredApplications().size());
info.put("totalInstances", applications.getRegisteredApplications().stream()
.mapToInt(app -> app.getInstances().size()).sum());
return info;
}
@GetMapping("/health")
public Map<String, Object> getClusterHealth() {
Map<String, Object> health = new HashMap<>();
try {
Applications applications = eurekaClient.getApplications();
long totalInstances = applications.getRegisteredApplications().stream()
.flatMap(app -> app.getInstances().stream())
.count();
long upInstances = applications.getRegisteredApplications().stream()
.flatMap(app -> app.getInstances().stream())
.filter(instance -> instance.getStatus() == InstanceInfo.InstanceStatus.UP)
.count();
health.put("status", totalInstances == upInstances ? "UP" : "DEGRADED");
health.put("totalInstances", totalInstances);
health.put("upInstances", upInstances);
health.put("downInstances", totalInstances - upInstances);
health.put("timestamp", System.currentTimeMillis());
} catch (Exception e) {
health.put("status", "DOWN");
health.put("error", e.getMessage());
}
return health;
}
}
性能优化
客户端缓存优化
eureka:
client:
# 获取服务列表间隔(默认30秒)
registry-fetch-interval-seconds: 10
# 是否启用增量获取
disable-delta: false
# 缓存刷新线程池大小
cache-refresh-executor-thread-pool-size: 2
# 缓存过期时间
cache-refresh-executor-exponential-back-off-bound: 10
服务端性能调优
eureka:
server:
# 响应缓存更新间隔(默认30秒)
response-cache-update-interval-ms: 10000
# 响应缓存自动过期时间(默认180秒)
response-cache-auto-expiration-in-seconds: 90
# 是否启用只读响应缓存
use-read-only-response-cache: true
# 清理任务间隔(默认60秒)
eviction-interval-timer-in-ms: 30000
# 同步注册表任务间隔
registry-sync-retry-wait-ms: 500
# 集群同步重试次数
number-of-replication-retries: 3
JVM参数优化
# Eureka Server JVM参数
java -Xms2g -Xmx2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UseStringDeduplication \
-Dcom.sun.management.jmxremote=true \
-Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar eureka-server.jar
# Eureka Client JVM参数
java -Xms1g -Xmx1g \
-XX:+UseG1GC \
-XX:+UseCompressedOops \
-Deureka.client.registryFetchIntervalSeconds=10 \
-jar user-service.jar
网络优化
@Configuration
public class EurekaNetworkConfig {
@Bean
public EurekaJerseyClient eurekaJerseyClient() {
EurekaJerseyClientBuilder builder = new EurekaJerseyClientBuilder();
builder.withClientName("eureka-client");
builder.withSystemSSLContext();
builder.withMaxConnectionsPerHost(50);
builder.withMaxTotalConnections(200);
builder.withConnectionTimeout(5000);
builder.withReadTimeout(10000);
builder.withConnectionIdleTimeout(30000);
return builder.build();
}
}
故障排查
常见问题诊断
@Component
@Slf4j
public class EurekaDiagnostics {
@Autowired
private EurekaClient eurekaClient;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void diagnose() {
diagnoseFetchRegistry();
diagnoseRegistration();
diagnoseHeartbeat();
}
private void diagnoseFetchRegistry() {
log.info("=== 注册表获取诊断 ===");
try {
Applications applications = eurekaClient.getApplications();
log.info("已获取应用数量: {}", applications.getRegisteredApplications().size());
for (Application app : applications.getRegisteredApplications()) {
log.info("应用: {}, 实例数量: {}", app.getName(), app.getInstances().size());
}
} catch (Exception e) {
log.error("获取注册表失败", e);
}
}
private void diagnoseRegistration() {
log.info("=== 服务注册诊断 ===");
String appName = applicationContext.getEnvironment().getProperty("spring.application.name");
if (appName != null) {
try {
Application application = eurekaClient.getApplication(appName.toUpperCase());
if (application != null) {
log.info("服务 {} 已注册,实例数量: {}", appName, application.getInstances().size());
} else {
log.warn("服务 {} 未找到注册信息", appName);
}
} catch (Exception e) {
log.error("检查服务注册状态失败", e);
}
}
}
private void diagnoseHeartbeat() {
log.info("=== 心跳诊断 ===");
try {
InstanceInfo instanceInfo = eurekaClient.getApplicationInfoManager().getInfo();
log.info("实例状态: {}", instanceInfo.getStatus());
log.info("实例ID: {}", instanceInfo.getInstanceId());
log.info("最后更新时间: {}", new Date(instanceInfo.getLastUpdatedTimestamp()));
} catch (Exception e) {
log.error("心跳诊断失败", e);
}
}
@EventListener
public void handleCacheRefresh(CacheRefreshedEvent event) {
log.info("缓存刷新事件: timestamp={}", event.getTimestamp());
}
@EventListener
public void handleStatusChange(StatusChangeEvent event) {
log.warn("实例状态变更: {} -> {}", event.getPreviousStatus(), event.getStatus());
if (event.getStatus() == InstanceInfo.InstanceStatus.DOWN) {
log.error("实例状态变为DOWN,请检查服务健康状态");
}
}
}
调试工具
@RestController
@RequestMapping("/debug/eureka")
@ConditionalOnProperty(name = "eureka.debug.enabled", havingValue = "true")
public class EurekaDebugController {
@Autowired
private EurekaClient eurekaClient;
@GetMapping("/applications")
public Applications getApplications() {
return eurekaClient.getApplications();
}
@GetMapping("/instance-info")
public InstanceInfo getInstanceInfo() {
return eurekaClient.getApplicationInfoManager().getInfo();
}
@GetMapping("/service-urls")
public List<String> getServiceUrls() {
return eurekaClient.getEurekaClientConfig().getEurekaServerServiceUrls("defaultZone");
}
@GetMapping("/local-registry")
public Map<String, Object> getLocalRegistry() {
Map<String, Object> registry = new HashMap<>();
Applications applications = eurekaClient.getApplications();
for (Application app : applications.getRegisteredApplications()) {
List<Map<String, Object>> instances = new ArrayList<>();
for (InstanceInfo instance : app.getInstances()) {
Map<String, Object> instanceInfo = new HashMap<>();
instanceInfo.put("instanceId", instance.getInstanceId());
instanceInfo.put("status", instance.getStatus());
instanceInfo.put("ipAddr", instance.getIPAddr());
instanceInfo.put("port", instance.getPort());
instanceInfo.put("lastUpdatedTimestamp", instance.getLastUpdatedTimestamp());
instanceInfo.put("actionType", instance.getActionType());
instances.add(instanceInfo);
}
registry.put(app.getName(), instances);
}
return registry;
}
@PostMapping("/refresh-registry")
public String refreshRegistry() {
try {
eurekaClient.getApplications(); // 触发缓存刷新
return "Registry refresh triggered";
} catch (Exception e) {
return "Registry refresh failed: " + e.getMessage();
}
}
}
监控告警
# Prometheus告警规则
groups:
- name: eureka-alerts
rules:
- alert: EurekaServerDown
expr: up{job="eureka-server"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Eureka Server is down"
description: "Eureka Server has been down for more than 1 minute"
- alert: EurekaHighInstanceChurn
expr: increase(eureka_server_registry_new_per_min[5m]) > 10
for: 2m
labels:
severity: warning
annotations:
summary: "High instance registration rate"
description: "Instance registration rate is {{ $value }} per minute"
- alert: EurekaLowInstanceCount
expr: eureka_registered_instances < 2
for: 5m
labels:
severity: warning
annotations:
summary: "Low number of registered instances"
description: "Only {{ $value }} instances are registered"
实战案例
电商微服务架构
# 用户服务配置
spring:
application:
name: user-service
eureka:
instance:
instance-id: ${spring.application.name}:${random.int}
prefer-ip-address: true
metadata-map:
version: v1.0
team: user-team
environment: production
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/
registry-fetch-interval-seconds: 10
---
# 订单服务配置
spring:
application:
name: order-service
eureka:
instance:
instance-id: ${spring.application.name}:${random.int}
prefer-ip-address: true
metadata-map:
version: v2.0
team: order-team
environment: production
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/
---
# API网关配置
spring:
application:
name: api-gateway
eureka:
instance:
instance-id: ${spring.application.name}:${random.int}
prefer-ip-address: true
metadata-map:
version: v1.0
team: gateway-team
environment: production
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/
服务调用示例
@Service
@Slf4j
public class OrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
public Order createOrder(CreateOrderRequest request) {
// 1. 调用用户服务验证用户
User user = getUserById(request.getUserId());
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 2. 调用商品服务验证商品
Product product = getProductById(request.getProductId());
if (product == null || product.getStock() < request.getQuantity()) {
throw new RuntimeException("商品库存不足");
}
// 3. 创建订单
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(product.getPrice().multiply(BigDecimal.valueOf(request.getQuantity())));
order.setStatus(OrderStatus.CREATED);
// 4. 保存订单
Order savedOrder = orderRepository.save(order);
// 5. 调用库存服务扣减库存
reduceStock(request.getProductId(), request.getQuantity());
log.info("订单创建成功: {}", savedOrder.getId());
return savedOrder;
}
private User getUserById(Long userId) {
try {
return restTemplate.getForObject("http://user-service/api/users/" + userId, User.class);
} catch (Exception e) {
log.error("调用用户服务失败", e);
return null;
}
}
private Product getProductById(Long productId) {
try {
return restTemplate.getForObject("http://product-service/api/products/" + productId, Product.class);
} catch (Exception e) {
log.error("调用商品服务失败", e);
return null;
}
}
private void reduceStock(Long productId, Integer quantity) {
try {
String url = "http://inventory-service/api/inventory/reduce";
ReduceStockRequest request = new ReduceStockRequest(productId, quantity);
restTemplate.postForObject(url, request, Void.class);
} catch (Exception e) {
log.error("调用库存服务失败", e);
throw new RuntimeException("库存扣减失败");
}
}
}
服务降级处理
@Component
@Slf4j
public class ServiceFallbackHandler {
@Autowired
private DiscoveryClient discoveryClient;
public User getUserFallback(Long userId) {
log.warn("用户服务降级,返回默认用户信息: userId={}", userId);
User fallbackUser = new User();
fallbackUser.setId(userId);
fallbackUser.setName("Unknown User");
fallbackUser.setStatus("UNKNOWN");
return fallbackUser;
}
public Product getProductFallback(Long productId) {
log.warn("商品服务降级,返回默认商品信息: productId={}", productId);
Product fallbackProduct = new Product();
fallbackProduct.setId(productId);
fallbackProduct.setName("Unknown Product");
fallbackProduct.setPrice(BigDecimal.ZERO);
fallbackProduct.setStock(0);
return fallbackProduct;
}
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void checkServiceHealth() {
checkService("user-service");
checkService("product-service");
checkService("inventory-service");
}
private void checkService(String serviceName) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
if (instances.isEmpty()) {
log.error("服务 {} 无可用实例", serviceName);
// 发送告警
sendAlert(serviceName, "No available instances");
} else {
long healthyInstances = instances.stream()
.filter(this::isInstanceHealthy)
.count();
if (healthyInstances == 0) {
log.error("服务 {} 所有实例都不健康", serviceName);
sendAlert(serviceName, "All instances unhealthy");
} else if (healthyInstances < instances.size()) {
log.warn("服务 {} 部分实例不健康: {}/{}", serviceName, healthyInstances, instances.size());
}
}
}
private boolean isInstanceHealthy(ServiceInstance instance) {
try {
String healthUrl = String.format("http://%s:%d/actuator/health",
instance.getHost(), instance.getPort());
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Map> response = restTemplate.getForEntity(healthUrl, Map.class);
Map<String, Object> health = response.getBody();
return "UP".equals(health.get("status"));
} catch (Exception e) {
log.debug("健康检查失败: {}:{}", instance.getHost(), instance.getPort());
return false;
}
}
private void sendAlert(String serviceName, String message) {
// 发送告警到监控系统
log.error("ALERT: Service {} - {}", serviceName, message);
}
}
最佳实践
1. 命名规范
# 应用命名规范
spring:
application:
name: ${TEAM_NAME}-${SERVICE_NAME}-${VERSION}
# 例如: user-team-user-service-v1
eureka:
instance:
# 实例ID规范
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
# 元数据规范
metadata-map:
version: ${app.version:1.0.0}
team: ${app.team:unknown}
environment: ${spring.profiles.active:dev}
build-time: ${app.build.time:unknown}
2. 环境配置
# 开发环境
---
spring:
profiles: dev
eureka:
instance:
lease-renewal-interval-in-seconds: 5 # 快速心跳
lease-expiration-duration-in-seconds: 10
client:
registry-fetch-interval-seconds: 5 # 快速发现
server:
enable-self-preservation: false # 关闭自我保护
eviction-interval-timer-in-ms: 5000 # 快速清理
# 生产环境
---
spring:
profiles: prod
eureka:
instance:
lease-renewal-interval-in-seconds: 30 # 标准心跳
lease-expiration-duration-in-seconds: 90
client:
registry-fetch-interval-seconds: 30 # 标准发现
server:
enable-self-preservation: true # 启用自我保护
eviction-interval-timer-in-ms: 60000 # 标准清理
3. 健康检查最佳实践
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Health health() {
Health.Builder builder = new Health.Builder();
try {
// 检查数据库连接
checkDatabase(builder);
// 检查Redis连接
checkRedis(builder);
// 检查外部依赖
checkExternalServices(builder);
return builder.up().build();
} catch (Exception e) {
return builder.down().withException(e).build();
}
}
private void checkDatabase(Health.Builder builder) throws SQLException {
try (Connection connection = dataSource.getConnection()) {
PreparedStatement statement = connection.prepareStatement("SELECT 1");
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
builder.withDetail("database", "UP");
}
}
}
private void checkRedis(Health.Builder builder) {
try {
redisTemplate.opsForValue().set("health:check", "ok", Duration.ofSeconds(5));
String result = redisTemplate.opsForValue().get("health:check");
if ("ok".equals(result)) {
builder.withDetail("redis", "UP");
}
} catch (Exception e) {
throw new RuntimeException("Redis health check failed", e);
}
}
private void checkExternalServices(Health.Builder builder) {
// 检查外部API、消息队列等
builder.withDetail("external-api", "UP");
}
}
4. 监控告警配置
@Configuration
public class EurekaMonitoringConfig {
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
@Bean
@EventListener
public void handleInstanceRegistered(InstanceRegisteredEvent<?> event) {
Metrics.counter("eureka.instance.registered",
"app", event.getInstanceInfo().getAppName()).increment();
}
@Bean
@EventListener
public void handleInstanceCanceled(EurekaInstanceCanceledEvent event) {
Metrics.counter("eureka.instance.canceled",
"app", event.getAppName()).increment();
}
}
5. 安全加固
@Configuration
@EnableWebSecurity
public class EurekaSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
.requestMatchers("/eureka/**").hasRole("EUREKA_CLIENT")
.anyRequest().authenticated())
.httpBasic()
.and()
.headers()
.frameOptions().sameOrigin()
.httpStrictTransportSecurity(hstsConfig ->
hstsConfig.maxAgeInSeconds(31536000).includeSubdomains(true));
return http.build();
}
}
总结
Netflix Eureka作为服务注册与发现的经典解决方案,虽然已停止维护,但在Spring Cloud生态中仍然被广泛使用。通过本文的学习,您应该掌握:
核心概念:理解Eureka的架构和工作原理
服务注册:掌握服务提供者的注册和配置
服务发现:掌握服务消费者的发现和调用
高可用配置:实现Eureka集群的高可用部署
安全配置:保护Eureka服务的安全访问
监控运维:监控Eureka集群的健康状态
故障排查:解决常见的Eureka问题
迁移建议
考虑到Eureka的维护状态,建议在新项目中考虑以下替代方案:
Consul:HashiCorp开源的服务发现解决方案
Nacos:阿里巴巴开源的动态服务发现、配置管理
Zookeeper:Apache ZooKeeper分布式协调服务
Kubernetes Service:云原生环境下的服务发现
进阶学习
服务网格:学习Istio、Linkerd等Service Mesh技术
云原生:掌握Kubernetes原生的服务发现机制
分布式系统:深入理解CAP定理和分布式一致性
微服务治理:学习完整的微服务治理方案
Eureka为微服务架构提供了稳定可靠的服务注册与发现能力,掌握其原理和实践对于理解微服务架构具有重要意义。
浙公网安备 33010602011771号