1.服务发现:Eureka 客户端

服务发现是微服务架构中的关键原则之一。尝试手动配置每个客户端或某种约定可能很难实现,且容易出现脆弱性问题。Eureka 是 Netflix 的服务发现服务器和客户端。该服务器可配置并部署为高可用状态,每个服务器会将注册服务的状态信息复制到其他服务器上。spring-doc.cadn.net.cn

1.1. 如何包含Eureka客户端

要在您的项目中包含 Eureka 客户端,请使用组 ID 为 org.springframework.cloud、构件 ID 为 spring-cloud-starter-netflix-eureka-client 的Starters。Spring Cloud 项目页面 可提供有关如何使用当前 Spring Cloud 发行版列车配置构建系统的详细信息。spring-doc.cadn.net.cn

1.2. 将应用注册到Eureka

当客户端向 Eureka 注册时,它会提供有关自身的元数据——例如主机、端口、健康指标 URL、主页及其他详细信息。</p><p>Eureka 会从属于某个服务的每个实例接收心跳消息。</p><p>如果在可配置的时间表内心跳失败,则该实例通常会被从注册表中移除。spring-doc.cadn.net.cn

以下示例展示了一个最小的 Eureka 客户端应用程序:spring-doc.cadn.net.cn

@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 应用程序。
由于类路径中包含 spring-cloud-starter-netflix-eureka-client,您的应用程序会自动向 Eureka 服务器注册。需要进行配置以定位 Eureka 服务器,如以下示例所示:spring-doc.cadn.net.cn

application.yml
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

在前面的示例中,defaultZone 是一个魔术字符串备用值,它为任何未表达偏好的客户端(即,这是一个有用的默认值)提供服务 URL。spring-doc.cadn.net.cn

属性 defaultZone 区分大小写,且必须使用驼峰命名法,因为 serviceUrl 属性是一个 Map<String, String>。因此,defaultZone 属性不符合 Spring Boot 默认的蛇形命名规范(即 default-zone)。

默认的应用名称(即服务ID)、虚拟主机和非安全端口(来自Environment)分别为${spring.application.name}${spring.application.name}${server.port}spring-doc.cadn.net.cn

在类路径中包含 spring-cloud-starter-netflix-eureka-client 会使应用程序同时成为 Eureka “实例”(即它会自动注册自身)和 “客户端”(它可以查询注册表以定位其他服务)。
实例行为由 eureka.instance.* 个配置项驱动,但若确保您的应用程序具有 spring.application.name 的值(这是 Eureka 服务 ID 或 VIP 的默认值),则使用默认配置即可。spring-doc.cadn.net.cn

请参阅 EurekaInstanceConfigBeanEurekaClientConfigBean,以获取有关可配置选项的更多详细信息。spring-doc.cadn.net.cn

要禁用 Eureka 发现客户端,可将 eureka.client.enabled 设置为 false。当 spring.cloud.discovery.enabled 设置为 false 时,Eureka 发现客户端也会被禁用。spring-doc.cadn.net.cn

将 Spring Cloud Netflix Eureka 服务器的版本指定为路径参数目前不被支持。这意味着您无法在上下文路径中设置版本(eurekaServerURLContext)。相反,您可以将版本包含在服务器 URL 中(例如,您可以设置 defaultZone: localhost:8761/eureka/v2)。

1.3.使用Eureka服务器进行身份验证

如果其中一个 eureka.client.serviceUrl.defaultZone URL 中嵌入了凭据(如 curl 风格所示:user:password@localhost:8761/eureka),则会自动为您的 Eureka 客户端添加 HTTP 基本认证。对于更复杂的需要,您可以创建一个类型为 DiscoveryClientOptionalArgs@Bean,并将 ClientFilter 实例注入其中,所有这些都将应用于客户端到服务器的调用。spring-doc.cadn.net.cn

当 Eureka 服务器需要客户端证书进行身份验证时,可以通过属性配置客户端证书和信任库,如下例所示:spring-doc.cadn.net.cn

application.yml
eureka:
  client:
    tls:
      enabled: true
      key-store: <path-of-key-store>
      key-store-type: PKCS12
      key-store-password: <key-store-password>
      key-password: <key-password>
      trust-store: <path-of-trust-store>
      trust-store-type: PKCS12
      trust-store-password: <trust-store-password>

需要将 eureka.client.tls.enabled 设置为 true 以启用 Eureka 客户端侧 TLS。当 eureka.client.tls.trust-store 被省略时,将使用 JVM 的默认信任库。默认值对于 eureka.client.tls.key-store-typeeureka.client.tls.trust-store-type 是 PKCS12。当密码属性被省略时,默认假设为空密码。spring-doc.cadn.net.cn

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

如果您想要自定义 Eureka HTTP 客户端所使用的 RestTemplate,可以创建一个 EurekaClientHttpRequestFactorySupplier 类型的 Bean,并提供您自己的逻辑来生成 ClientHttpRequestFactory 实例。spring-doc.cadn.net.cn

所有Eureka HTTP客户端使用的RestTemplate的默认超时相关属性均设置为无限。因此,若要指定超时值,必须直接通过在eureka.client.rest-template-timeout属性中指定值来设置。(所有超时属性均以毫秒为单位。)spring-doc.cadn.net.cn

application.yml
eureka:
  client:
    rest-template-timeout:
      connect-timeout: 5000
      connect-request-timeout: 8000
      socket-timeout: 10000

1.4. 状态页和健康指标

Eureka 实例的状态页面和健康指标默认值分别为 /info/health,这两个值是 Spring Boot Actuator 应用中常用端点的默认位置。如果您使用非默认的上下文路径或 Servlet 路径(例如 server.servletPath=/custom),即使是在 Actuator 应用中,也必须对其进行更改。以下示例展示了这两个设置的默认值:spring-doc.cadn.net.cn

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

这些链接会出现在客户端所使用的元数据中,并在某些场景下用于决定是否向您的应用程序发送请求,因此如果它们准确无误将非常有帮助。spring-doc.cadn.net.cn

在 Dalston 版本中,当更改管理上下文路径时,也必须设置状态和健康检查 URL。此要求自 Edgware 版本起已被移除。

1.5. 注册安全应用程序

如果您的应用程序希望通过 HTTPS 进行通信,您可以在 EurekaInstanceConfigBean 中设置两个标志:spring-doc.cadn.net.cn

这样做会使 Eureka 发布实例信息,以显示对安全通信的明确偏好。
Spring Cloud DiscoveryClient 始终为按此方式配置的服务返回一个以 https 开头的 URI。
同样,当服务按此方式配置时,Eureka(原生)实例信息中会包含一个安全的健康检查 URL。spring-doc.cadn.net.cn

由于 Eureka 内部的工作方式,它仍然会发布一个非安全的 URL 来提供状态页和主页,除非您也显式地覆盖这些页面。您可以使用占位符来配置 Eureka 实例的 URL,如下所示示例:spring-doc.cadn.net.cn

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}。)spring-doc.cadn.net.cn

如果您的应用程序位于代理之后,且 SSL 终止由代理完成(例如,在 Cloud Foundry 或其他平台即服务环境中运行),则需要确保代理的“转发”头信息被应用程序捕获并处理。</p><p>如果嵌入在 Spring Boot 应用程序中的 Tomcat 容器对 'X-Forwarded-*' 头部有显式配置,则此操作会自动完成。</p><p>您应用程序自身生成的链接出现错误(如主机名、端口或协议不正确)表明该配置存在错误。

1.6. Eureka 的健康检查

默认情况下,Eureka 使用客户端心跳来判断客户端是否处于活跃状态。除非另有指定,Discovery Client 不会根据 Spring Boot Actuator 的当前健康检查状态传播应用的健康状况。因此,在成功注册后,Eureka 始终将应用状态宣告为“UP”(运行中)。通过启用 Eureka 健康检查,可以改变此行为,从而将应用的状态传播至 Eureka。结果是,其他所有应用仅向状态为“UP”的应用发送流量。以下示例展示了如何为客户端启用健康检查:spring-doc.cadn.net.cn

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

如果您需要对健康检查拥有更多控制权,可考虑实现自己的 com.netflix.appinfo.HealthCheckHandlerspring-doc.cadn.net.cn

1.7. Eureka 实例和客户端元数据

值得花一点时间理解 Eureka 元数据的工作原理,以便在您的平台中以合理的方式使用它。对于诸如主机名、IP 地址、端口号、状态页面和健康检查等信息,存在标准元数据。这些元数据会被发布到服务注册表中,并被客户端用于以简单直接的方式与服务进行通信。还可以在实例注册的 eureka.instance.metadataMap 中添加额外的元数据,这些元数据可在远程客户端中访问。通常情况下,额外的元数据不会改变客户端的行为,除非客户端已知晓该元数据的含义。本文档后文将介绍几个特殊情况,其中 Spring Cloud 已为元数据映射赋予了特定含义。spring-doc.cadn.net.cn

1.7.1. 在Cloud Foundry上使用Eureka

Cloud Foundry 拥有一个全局路由器,使得同一应用的所有实例都拥有相同的主机名(其他具有类似架构的 PaaS 解决方案也采用相同的安排)。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

这并不必然构成使用 Eureka 的障碍。spring-doc.cadn.net.cn

然而,如果您使用路由器(根据您的平台部署方式,这可能是推荐的,甚至是强制性的),则需要显式设置主机名和端口号(包括安全或非安全端口),以确保它们通过路由器进行通信。spring-doc.cadn.net.cn

您可能还想使用实例元数据,以便在客户端区分不同实例(例如,在自定义负载均衡器中)。spring-doc.cadn.net.cn

默认情况下,eureka.instance.instanceIdvcap.application.instance_id,如以下示例所示:spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

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

根据您的 Cloud Foundry 实例中安全规则的配置方式,您可能能够注册并使用主机 VM 的 IP 地址,以实现服务间的直接调用。此功能目前尚未在 Pivotal Web Services(PWS)上提供。spring-doc.cadn.net.cn

1.7.2. 在AWS上使用Eureka

如果计划将应用程序部署到 AWS 云环境,则 Eureka 实例必须配置为具备 AWS 识别能力。您可以通过如下方式自定义 EurekaInstanceConfigBeanspring-doc.cadn.net.cn

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

1.7.3.更改Eureka实例ID

一个原生的 Netflix Eureka 实例以与其主机名相同的 ID 进行注册(即每台主机上仅有一个服务)。</p><p>Spring Cloud Eureka 提供了一个合理的默认配置,其定义如下:spring-doc.cadn.net.cn

${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}spring-doc.cadn.net.cn

一个例子是 myhost:myappname:8080spring-doc.cadn.net.cn

通过使用 Spring Cloud,您可以通过提供一个唯一标识符来覆盖此值,如以下示例所示:eureka.instance.instanceIdspring-doc.cadn.net.cn

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

在前面示例中显示的元数据以及在 localhost 上部署多个服务实例的情况下,随机值会被插入其中以确保每个实例的唯一性。在 Cloud Foundry 中,Spring Boot 应用会自动填充 vcap.application.instance_id,因此无需使用随机值。spring-doc.cadn.net.cn

2.8. 使用 EurekaClient

一旦您拥有一个作为发现客户端的应用程序,就可以使用它从 Eureka Server 中发现服务实例。一种实现方式是使用原生的 com.netflix.discovery.EurekaClient(而非 Spring Cloud 的 DiscoveryClient),如以下示例所示:spring-doc.cadn.net.cn

@Autowired
private EurekaClient discoveryClient;

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

不要在 EurekaClient 方法或 @Scheduled 方法(或其他可能尚未启动 ApplicationContext 的位置)中使用 EurekaClient。它在 SmartLifecycle 中被初始化(通过 phase=0),因此您最早可依赖其可用性是在另一个具有更高阶段的 SmartLifecycle 中。spring-doc.cadn.net.cn

1.8.1. Eureka 客户端与 Jersey

默认情况下,EurekaClient 使用 Spring 的 RestTemplate 进行 HTTP 通信。 如果你希望使用 Jersey,需要将 Jersey 依赖项添加到你的类路径中。 以下示例显示了你需要添加的依赖项:spring-doc.cadn.net.cn

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
</dependency>
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-apache-client4</artifactId>
</dependency>

1.9.替代原生 Netflix EurekaClient 的方案

您无需直接使用原始的 Netflix EurekaClient。此外,通常更方便的是将其置于某种包装器之后使用。Spring Cloud 通过逻辑 Eureka 服务标识符(VIPs)而非物理 URL,为 Feign(一个 REST 客户端构建器)和 Spring Cloud LoadBalancer 提供了支持。spring-doc.cadn.net.cn

您还可以使用 org.springframework.cloud.client.discovery.DiscoveryClient,它提供了一个简单的 API(非特定于 Netflix),用于服务发现客户端,如下例所示:spring-doc.cadn.net.cn

@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;
}

<h2>1.10. 为什么注册服务如此之慢?</h2>

作为实例的一部分,还涉及定期向注册中心发送心跳(通过客户端的 serviceUrl),默认持续时间为 30 秒。服务在实例、服务器和客户端三者本地缓存中的元数据一致之前,无法被客户端发现(因此可能需要 3 次心跳)。您可以通过设置 eureka.instance.leaseRenewalIntervalInSeconds 来更改此周期。将其设置为小于 30 的值可加快客户端连接到其他服务的速度。在生产环境中,建议仍使用默认值,因为服务器内部存在一些计算,这些计算依赖于租约续期时间的假设。spring-doc.cadn.net.cn

1.11.区域

如果您已将 Eureka 客户端部署到多个可用区,您可能更希望这些客户端优先使用同一可用区内的服务,然后再尝试其他可用区中的服务。要实现这一点,您需要正确配置您的 Eureka 客户端。spring-doc.cadn.net.cn

首先,您需要确保每个可用区都部署了 Eureka 服务器,并且它们相互为对等节点。
有关更多信息,请参阅 可用区和区域 部分。spring-doc.cadn.net.cn

接下来,您需要告诉 Eureka 您的服务位于哪个区域。您可以通过使用 metadataMap 属性来实现此目的。例如,如果 service 1 部署在 zone 1zone 2 中,则您需要在 service 1 中设置以下 Eureka 属性:spring-doc.cadn.net.cn

服务 1 在区域 1spring-doc.cadn.net.cn

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

服务 1 在区域 2spring-doc.cadn.net.cn

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

1.12. 刷新Eureka客户端

默认情况下,EurekaClient 个 Bean 是可刷新的,这意味着 Eureka 客户端属性可以被更改并刷新。当发生刷新时,客户端将从 Eureka 服务器上取消注册,并可能有一段短暂的时间,在此期间,给定服务的所有实例均不可用。消除这种情况的一种方法是禁用刷新 Eureka 客户端的功能。为此,请设置 eureka.client.refresh.enable=falsespring-doc.cadn.net.cn

1.13. 使用Spring Cloud LoadBalancer与Eureka

我们提供对 Spring Cloud LoadBalancer ZonePreferenceServiceInstanceListSupplier 的支持。Eureka 实例元数据中的 zone 值(eureka.instance.metadataMap.zone)用于设置 spring-cloud-loadbalancer-zone 属性的值,该属性用于按区域过滤服务实例。spring-doc.cadn.net.cn

如果缺失且设置了 spring.cloud.loadbalancer.eureka.approximateZoneFromHostname 标志为 true,它可使用服务器主机名中的域名作为区域的代理。spring-doc.cadn.net.cn

如果没有其他区域数据来源,则根据客户端配置(而非实例配置)进行猜测。我们取 eureka.client.availabilityZones,这是一个从区域名称到区域列表的映射,并提取实例所在区域的第一个区域(即 eureka.client.region,其默认值为 "us-east-1",以保持与原生 Netflix 的兼容性)。spring-doc.cadn.net.cn