On this page
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 的默认值),则默认值很好。
有关可配置选项的更多详细信息,请参见EurekaInstanceConfigBean和EurekaClientConfigBean。
要禁用 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.instanceId
为vcap.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 1
和zone 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