11. 服务发现:EurekaClient

服务发现是基于微服务的体系结构的主要宗旨之一。尝试手动配置每个 Client 端或某种形式的约定可能很困难并且很脆弱。 Eureka 是 Netflix 服务发现服务器和 Client 端。可以将服务器配置和部署为高度可用,每个服务器将有关已注册服务的状态复制到其他服务器。

11.1 如何包括 Eureka Client

要将 Eureka Client 包含在您的项目中,请使用启动器,其组 ID 为org.springframework.cloud且构件 ID 为spring-cloud-starter-netflix-eureka-client。有关使用当前 Spring Cloud Release Train 设置构建系统的详细信息,请参见Spring Cloud Project 页面

11.2 向 Eureka 注册

当 Client 端向 Eureka 注册时,它将提供有关其自身的元数据,例如主机,端口,运行状况指示器 URL,主页和其他详细信息。 Eureka 从属于服务的每个实例接收心跳消息。如果心跳在可配置的时间表上进行故障转移,则通常会将实例从注册表中删除。

以下示例显示了一个最小的 EurekaClient 端应用程序:

@SpringBootApplication
@RestController
public class Application {

    @RequestMapping("/")
    public String home() {
        return "Hello world";
    }

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}

请注意,前面的示例显示了普通的Spring Boot应用程序。通过在 Classpath 上使用spring-cloud-starter-netflix-eureka-client,您的应用程序将自动向 Eureka Server 注册。需要进行配置才能找到 Eureka 服务器,如以下示例所示:

application.yml.

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

在前面的示例中,“ defaultZone”是一个魔术字符串后备值,它为任何不表达首选项的 Client 端提供服务 URL(换句话说,这是一个有用的默认值)。

默认应用程序名称(即服务 ID),虚拟主机和非安全端口(从Environment获取)分别为${spring.application.name}${spring.application.name}${server.port}

在 Classpath 上具有spring-cloud-starter-netflix-eureka-client可使该应用程序同时进入 Eureka 的“实例”(即,它自己注册)和“Client 端”(它可以查询注册表以定位其他服务)。实例行为由eureka.instance.*配置键驱动,但是如果确保您的应用程序具有spring.application.name的值(这是 Eureka 服务 ID 或 VIP 的默认值),则默认值很好。

有关可配置选项的更多详细信息,请参见EurekaInstanceConfigBeanEurekaClientConfigBean

要禁用 Eureka Discovery Client,可以将eureka.client.enabled设置为false

11.3 通过 Eureka 服务器进行身份验证

如果eureka.client.serviceUrl.defaultZone URL 之一中嵌入了凭据(curl 样式,如下:http://user:[email protected]:8761/eureka),则会将 HTTP 基本身份验证自动添加到您的 eurekaClient 端。对于更复杂的需求,您可以创建类型为DiscoveryClientOptionalArgs@Bean并将ClientFilter实例注入其中,所有实例都应用于从 Client 端到服务器的调用。

Note

由于 Eureka 的限制,无法支持每服务器的基本身份验证凭据,因此仅使用找到的第一组凭据。

11.4 状态页和运行状况指示器

Eureka 实例的状态页面和运行状况指示器分别默认为/info/health,它们是 Spring Boot Actuator 应用程序中有用端点的默认位置。即使您使用非默认上下文路径或 Servlet 路径(例如server.servletPath=/custom),也需要更改这些内容,即使对于 Actuator 应用程序也是如此。下面的示例显示两个设置的默认值:

application.yml.

eureka:
  instance:
    statusPageUrlPath: ${server.servletPath}/info
    healthCheckUrlPath: ${server.servletPath}/health

这些链接显示在 Client 端使用的元数据中,并在某些情况下用于确定是否将请求发送到您的应用程序,因此,如果请求准确无误,这将很有帮助。

Note

在 Dalston 中,还需要在更改该 Management 上下文路径时设置状态和运行状况检查 URL。从 Edgware 开始就删除了此要求。

11.5 注册安全应用程序

如果您想通过 HTTPS 与您的应用进行联系,则可以在EurekaInstanceConfig中设置两个标志:

  • eureka.instance.[nonSecurePortEnabled]=[false]

  • eureka.instance.[securePortEnabled]=[true]

这样做会使 Eureka 发布实例信息,该实例信息显示出对安全通信的明确偏好。对于以这种方式配置的服务,Spring Cloud DiscoveryClient始终返回以https开头的 URI。同样,以这种方式配置服务时,Eureka(本机)实例信息具有安全的运行状况检查 URL。

由于 Eureka 在内部工作的方式,它仍然会为状态和主页发布非安全 URL,除非您也明确地覆盖了这些 URL。您可以使用占位符来配置 eureka 实例 URL,如以下示例所示:

application.yml.

eureka:
  instance:
    statusPageUrl: https://${eureka.hostname}/info
    healthCheckUrl: https://${eureka.hostname}/health
    homePageUrl: https://${eureka.hostname}/

(请注意,${eureka.hostname}是本机占位符,仅在更高版本的 Eureka 中可用.您也可以使用 Spring 占位符来实现相同的功能-例如使用${eureka.instance.hostName}.)

Note

如果您的应用程序在代理后面运行,并且 SSL 终止在代理中(例如,如果您在 Cloud Foundry 或其他平台中作为服务运行),则需要确保代理被“转发”的 Headers 被拦截和处理通过应用程序。如果嵌入在 Spring Boot 应用程序中的 Tomcat 容器具有针对'X-Forwarded-\ *`Headers 的显式配置,则此操作会自动发生。应用程序提供的指向自身的链接错误(错误的主机,端口或协议)表明此配置错误。

11.6Eureka 的健康检查

默认情况下,Eureka 使用 Client 端心跳来确定 Client 端是否启动。除非另有说明,否则按照 Spring Boot Actuator 的规定,Discovery Client 不会传播应用程序的当前运行状况检查状态。因此,在成功注册后,Eureka 始终宣布该应用程序处于“启动”状态。可以通过启用 Eureka 运行状况检查来更改此行为,这会导致应用程序状态传播到 Eureka。结果,其他所有应用程序都不会将流量发送到“ UP”以外的其他状态的应用程序。以下示例显示如何为 Client 端启用运行状况检查:

application.yml.

eureka:
  client:
    healthcheck:
      enabled: true

Warning

eureka.client.healthcheck.enabled=true只应在application.yml中设置。在bootstrap.yml中设置该值会导致不良的副作用,例如以UNKNOWN状态在 Eureka 中注册。

如果您需要对运行状况检查进行更多控制,请考虑实施自己的com.netflix.appinfo.HealthCheckHandler

11.7 实例和 Client 端的 Eureka 元数据

值得花费一些时间来了解 Eureka 元数据的工作原理,因此您可以在平台上合理使用它。有用于信息的标准元数据,例如主机名,IP 地址,端口号,状态页和运行状况检查。这些都发布在服务注册表中,并由 Client 端用于以直接方式联系服务。可以将其他元数据添加到eureka.instance.metadataMap的实例注册中,并且可以在远程 Client 端中访问此元数据。通常,除非让 Client 端了解元数据的含义,否则其他元数据不会更改 Client 端的行为。在本文档后面将介绍几种特殊情况,其中 Spring Cloud 已经为元数据 Map 分配了含义。

11.7.1 在 Cloud Foundry 上使用 Eureka

Cloud Foundry 具有全局 Router,因此同一应用程序的所有实例都具有相同的主机名(其他具有类似体系结构的 PaaS 解决方案具有相同的排列)。这不一定是使用 Eureka 的障碍。但是,如果您使用 Router(建议或什至是强制性的,取决于平台的设置方式),则需要显式设置主机名和端口号(安全或非安全),以便它们使用 Router。您可能还希望使用实例元数据,以便可以区分 Client 端上的实例(例如,在自定义负载平衡器中)。默认情况下,eureka.instance.instanceIdvcap.application.instance_id,如以下示例所示:

application.yml.

eureka:
  instance:
    hostname: ${vcap.application.uris[0]}
    nonSecurePort: 80

根据在 Cloud Foundry 实例中设置安全规则的方式,您可能可以注册并使用主机 VM 的 IP 地址进行直接的服务到服务的调用。 Pivotal Web 服务(PWS)尚不提供此功能。

11.7.2 在 AWS 上使用 Eureka

如果计划将应用程序部署到 AWS 云,则必须将 Eureka 实例配置为可感知 AWS。您可以通过如下自定义EurekaInstanceConfigBean来实现:

@Bean
@Profile("!default")
public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) {
  EurekaInstanceConfigBean b = new EurekaInstanceConfigBean(inetUtils);
  AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
  b.setDataCenterInfo(info);
  return b;
}

11.7.3 更改 Eureka 实例 ID

一个普通的 Netflix Eureka 实例注册的 ID 等于其主机名(即,每个主机仅提供一项服务)。 Spring Cloud Eureka 提供了明智的默认值,其定义如下:

${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}}

例如myhost:myappname:8080

通过使用 Spring Cloud,您可以通过在eureka.instance.instanceId中提供唯一的标识符来覆盖此值,如以下示例所示:

application.yml.

eureka:
  instance:
    instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}

通过前面示例中显示的元数据和在 localhost 上部署的多个服务实例,在其中插入随机值以使实例唯一。在 Cloud Foundry 中,vcap.application.instance_id是在 Spring Boot 应用程序中自动填充的,因此不需要随机值。

11.8 使用 EurekaClient

一旦拥有作为发现 Client 端的应用程序,就可以使用它从Eureka Server发现服务实例。一种方法是使用本机com.netflix.discovery.EurekaClient(与 Spring Cloud DiscoveryClient相对),如以下示例所示:

@Autowired
private EurekaClient discoveryClient;

public String serviceUrl() {
    InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false);
    return instance.getHomePageUrl();
}

Tip

请勿在@PostConstruct方法或@Scheduled方法(或ApplicationContext可能尚未启动的任何地方)中使用EurekaClient。它以SmartLifecycle(带有phase=0)初始化,因此最早可以依靠它的是另一个具有较高相位的SmartLifecycle

11.8.1 不含Jersey的 EurekaClient

默认情况下,EurekaClient 使用 Jersey 进行 HTTP 通信。如果希望避免来自 Jersey 的依赖关系,可以将其从依赖关系中排除。 Spring Cloud 基于 Spring RestTemplate自动配置传输 Client 端。以下示例显示排除了 Jersey:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <exclusions>
        <exclusion>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
        </exclusion>
        <exclusion>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>com.sun.jersey.contribs</groupId>
            <artifactId>jersey-apache-client4</artifactId>
        </exclusion>
    </exclusions>
</dependency>

11.9 替代本机 Netflix EurekaClient

您无需使用原始 Netflix EurekaClient。而且,通常在某种包装器后面使用它会更方便。 Spring Cloud 通过逻辑 Eureka 服务标识符(VIP)而非物理 URL 支持Feign(RESTClient 端构建器)和Spring RestTemplate。要使用固定的物理服务器列表配置 Ribbon,可以将<client>.ribbon.listOfServers设置为以逗号分隔的物理地址(或主机名)列表,其中<client>是 Client 端的 ID。

您还可以使用org.springframework.cloud.client.discovery.DiscoveryClient,它为发现 Client 端提供简单的 API(非 Netflix 专用),如以下示例所示:

@Autowired
private DiscoveryClient discoveryClient;

public String serviceUrl() {
    List<ServiceInstance> list = discoveryClient.getInstances("STORES");
    if (list != null && list.size() > 0 ) {
        return list.get(0).getUri();
    }
    return null;
}

11.10 为什么注册服务这么慢?

成为实例还涉及到注册表的定期心跳(通过 Client 端的serviceUrl),默认持续时间为 30 秒。直到实例,服务器和 Client 端在其本地缓存中都具有相同的元数据后,Client 端才能发现该服务(因此可能需要 3 个心跳)。您可以通过设置eureka.instance.leaseRenewalIntervalInSeconds来更改周期。将其设置为小于 30 的值可以加快使 Client 端连接到其他服务的过程。在 Producing,最好使用默认值,因为服务器中的内部计算对租约续订期进行了假设。

11.11 Zones

如果您已将 EurekaClient 端部署到多个区域,则您可能希望这些 Client 端在尝试使用其他区域中的服务之前先使用同一区域中的服务。要进行设置,您需要正确配置 EurekaClient 端。

首先,您需要确保已将 Eureka 服务器部署到每个区域,并且它们彼此对等。有关更多信息,请参见区域和地区部分。

接下来,您需要告诉 Eureka 您的服务位于哪个区域。您可以使用metadataMap属性来做到这一点。例如,如果将service 1同时部署到zone 1zone 2,则需要在service 1中设置以下 Eureka 属性:

区域 1 中的服务 1

eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true

区域 2 中的服务 1

eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true