11. 服务发现:Eureka Clients

服务发现是 microservice-based architecture 的 key 原则之一。试图 hand-configure 每个客户或某种形式的惯例可能很难做到并且可能很脆弱。 Eureka 是 Netflix 服务发现服务器和 Client。可以配置和部署服务器以使其具有高可用性,每个服务器将注册服务的 state 复制到其他服务器。

11.1 如何包含 Eureka Client

要在项目中包含 Eureka Client,请使用 group ID 为org.springframework.cloud且 artifact ID 为spring-cloud-starter-netflix-eureka-client的 starter。有关使用当前 Spring Cloud Release Train 设置 build 系统的详细信息,请参阅Spring Cloud 项目页面

11.2 注册 Eureka

当 client 向 Eureka 注册时,它会提供 meta-data 自身 - 例如 host,port,运行状况指示器 URL,主页和其他详细信息。 Eureka 从属于服务的每个实例接收心跳消息。如果心跳故障超过可配置的时间表,则通常会从注册表中删除该实例。

以下 example 显示了一个最小的 Eureka client application:

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

}

请注意,前面的 example 显示了一个普通的Spring Boot application。通过在 classpath 上使用spring-cloud-starter-netflix-eureka-client,您的 application 会自动向 Eureka Server 注册。 Configuration 是定位 Eureka 服务器所必需的,如下面的示例所示:

application.yml.

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

在前面的 example 中,“defaultZone”是一个 magic string fallback value,它为任何不表示首选项的 client 提供服务 URL(换句话说,它是一个有用的默认值)。

默认的 application name(即服务 ID),virtual host 和 non-secure port(取自Environment)分别是${spring.application.name}${spring.application.name}${server.port}

在 classpath 上有spring-cloud-starter-netflix-eureka-client使应用程序成为 Eureka“实例”(即,它自己注册)和“client”(它可以查询注册表以查找其他服务)。实例行为由eureka.instance.* configuration 键驱动,但如果确保 application 具有spring.application.name的 value(这是 Eureka 服务 ID 或 VIP 的默认值),则默认值很好。

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

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

11.3 使用 Eureka Server 进行身份验证

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

由于 Eureka 中的限制,因此无法支持 per-server 基本身份验证凭据,因此仅使用找到的第一个集合。

11.4 状态页面和健康指标

Eureka 实例的状态页面和运行状况指示器分别默认为/info/health,它们是 Spring Boot Actuator application 中有用 endpoints 的默认位置。如果使用 non-default context 路径或 servlet 路径(例如server.servletPath=/custom),则需要更改这些,即使对于 Actuator application 也是如此。以下 example 显示了两个设置的默认值:

application.yml.

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

这些链接显示在 clients 使用的元数据中,并在某些情况下用于决定是否向 application 发送请求,因此如果它们准确,则会很有帮助。

在 Dalston 中,还需要在更改 management context 路径时设置状态和运行状况检查 URL。从 Edgware 开始删除此要求。

11.5 注册安全 Application

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

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

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

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

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

application.yml.

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

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

如果您的 application 在代理后运行,并且 SSL 终止在代理中(例如,如果您在 Cloud Foundry 或其他平台中作为服务运行),那么您需要确保代理“转发”headers 被拦截和处理通过 application。如果嵌入在 Spring Boot application 中的 Tomcat 容器具有'X-Forwarded - *`headers 的显式 configuration,则会自动发生这种情况。您的应用程序呈现给自己错误的链接(错误的 host,port 或协议)表明您的配置错误。

11.6 Eureka 的健康检查

默认情况下,Eureka 使用 client 心跳来确定 client 是否已启动。除非另有说明,否则 Discovery Client 不会根据 Spring Boot Actuator 传播 application 的当前运行状况检查状态。因此,在成功注册后,Eureka 总是宣布 application 处于'UP'state 状态。通过启用 Eureka 运行状况检查可以更改此行为,从而将 application 状态传播到 Eureka。因此,除了'UP'之外的其他状态,每个其他 application 都不会向 applications 发送流量。以下 example 显示了如何为 client 启用运行状况检查:

application.yml.

eureka:
  client:
    healthcheck:
      enabled: true

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

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

实例和 Clients 的 11.7 Eureka 元数据

值得花一点时间了解 Eureka 元数据的工作原理,因此您可以在平台中使用它。存在标准元数据,例如主机名,IP 地址,端口数字,状态页面和运行状况检查。这些发布在服务注册表中,并由 clients 用于以直接的方式联系服务。可以将其他元数据添加到eureka.instance.metadataMap中的实例注册中,并且可以在 remote clients 中访问此元数据。通常,除非 client 了解元数据的含义,否则其他元数据不会更改 client 的行为。有几个特殊情况,稍后将在本文档中进行描述,其中 Spring Cloud 已经为元数据 map 赋予了意义。

11.7.1 在 Cloud Foundry 上使用 Eureka

Cloud Foundry 有一个 global router,以便同一个应用程序的所有实例具有相同的主机名(具有类似 architecture 的其他 PaaS 解决方案具有相同的排列)。这不一定是使用 Eureka 的障碍。但是,如果使用 router(建议甚至强制,具体取决于平台的设置方式),则需要显式设置 hostname 和 port numbers(secure 或 non-secure),以便它们使用 router。您可能还希望使用实例元数据,以便区分 client 上的实例(对于 example,在自定义负载均衡器中)。默认情况下,eureka.instance.instanceIdvcap.application.instance_id,如下面的示例所示:

application.yml.

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

根据在 Cloud Foundry 实例中设置安全规则的方式,您可以注册并使用 host VM 的 IP 地址直接 service-to-service calls。 Pivotal Web Services(PWS)上尚未提供此 feature。

11.7.2 在 AWS 上使用 Eureka

如果计划将 application 部署到 AWS 云,则必须将 Eureka 实例配置为 AWS-aware。您可以通过自定义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

一个 vanilla Netflix Eureka 实例注册的 ID 等于其 host name(即每个 host 只有一个服务)。 Spring Cloud Eureka 提供合理的默认值,定义如下:

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

示例是myhost:myappname:8080

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

application.yml.

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

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

11.8 使用 EurekaClient

一旦你有一个 application 是一个发现 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();
}

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

11.8.1 没有 Jersey 的 EurekaClient

默认情况下,EurekaClient 使用 Jersey 进行 HTTP 通信。如果您希望避免来自 Jersey 的依赖项,则可以将其从依赖项中排除。 Spring Cloud auto-configures 基于 Spring RestTemplate的传输客户端。以下示例显示 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 Native Netflix EurekaClient 的替代品

您无需使用原始 Netflix EurekaClient。此外,在某种 wrapper 后面使用它通常更方便。 Spring Cloud 支持假装(REST 客户端构建器)和Spring RestTemplate通过逻辑 Eureka 服务标识符(VIP)而不是物理 URL。要使用固定的物理服务器列表配置 Ribbon,可以将<client>.ribbon.listOfServers设置为 comma-separated 物理地址(或主机名)列表,其中<client>是 client 的 ID。

您还可以使用org.springframework.cloud.client.discovery.DiscoveryClient,它为 discovery clients 提供了一个简单的 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 在其本地缓存中都具有相同的元数据之前,clients 无法发现服务(因此它可能需要 3 个心跳)。您可以通过设置eureka.instance.leaseRenewalIntervalInSeconds来更改周期。将其设置为小于 30 的 value 会加快_-clients 连接到其他服务的 process。在 production 中,最好坚持使用默认值,因为服务器中的内部计算会对租约续订期做出假设。

11.11 区域

如果已将 Eureka clients 部署到多个区域,您可能希望这些 clients 在尝试另一个 zone 中的服务之前使用同一 zone 内的服务。要进行此设置,您需要正确配置 Eureka clients。

首先,您需要确保将 Eureka 服务器部署到每个 zone 并且它们是彼此的对等体。有关详细信息,请参阅区域和地区部分。

接下来,您需要告诉 Eureka 您的服务所在的区域。您可以使用metadataMap property 来执行此操作。例如,如果service 1部署到zone 1zone 2,则需要在service 1中设置以下 Eureka properties:

Zone 1中的服务 1

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

Zone 2中的服务 1

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