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