服务发现:Eureka 客户端
服务发现是基于微服务架构的关键原理之一,尝试手动配置每个客户端或某种形式的约定可能很难做到并且可能很脆弱,Eureka 是 Netflix Service Discovery 服务器和客户端,服务器可以被配置和部署为高可用性,每个服务器将注册服务的状态复制到其他服务器。
如何包含 Eureka 客户端
要在项目中包含 Eureka Client,请使用组 ID 为 org.springframework.cloud 和工件 ID 为 spring-cloud-starter-netflix-eureka-client 的启动器。
注册 Eureka
当客户端向 Eureka 注册时,它会提供有关自身的元数据 — 例如主机、端口、健康指示器 URL、主页和其他详细信息,Eureka 从属于服务的每个实例接收心跳消息,如果心跳故障超过可配置的时间表,则通常会从注册表中删除该实例。
以下示例显示了最小的 Eureka 客户端应用程序:
@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 Server 注册,找到 Eureka 服务器需要进行配置,如以下示例所示:
application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
在前面的示例中,“defaultZone”是一个神奇的字符串回退值,它为任何不表示首选项的客户端提供服务 URL(换句话说,它是一个有用的默认值)。
默认应用程序名称(即服务 ID)、虚拟主机和非安全端口(取自 Environment)分别是 ${spring.application.name}、${spring.application.name} 和 ${server.port}。
在类路径上使用 spring-cloud-starter-netflix-eureka-client 使应用程序成为 Eureka“实例”(即,它自己注册)和“客户端”(它可以查询注册表以查找其他服务),实例行为由 eureka.instance.* 配置键驱动,但如果你确保应用程序具有 spring.application.name 的值(这是 Eureka 服务 ID 或 VIP 的默认值),则默认值很好。
有关可配置选项的更多详细信息,请参阅 EurekaInstanceConfigBean 和 EurekaClientConfigBean。
要禁用 Eureka Discovery Client,可以将 eureka.client.enabled 设置为 false。
使用 Eureka Server 进行身份验证
如果其中一个 eureka.client.serviceUrl.defaultZone URL 中嵌入了凭据,则会自动将 HTTP 基本身份验证添加到你的 eureka 客户端(curl 样式,如下所示:http://user:password@localhost:8761/eureka)。对于更复杂的需求,你可以创建一个类型为 DiscoveryClientOptionalArgs 的 @Bean 并将 ClientFilter 实例注入其中,所有这些都应用于从客户端到服务器的调用。
由于 Eureka 的限制,无法支持每个服务器基本身份验证凭据,因此仅使用找到的第一个集合。
状态页面和健康指示器
Eureka 实例的状态页面和健康指示器分别默认为 /info 和 /health,它们是 Spring Boot Actuator 应用程序中有用端点的默认位置,如果使用非默认上下文路径或 servlet 路径(例如 server.servletPath=/custom),则需要更改这些,即使对于 Actuator 应用程序也是如此,以下示例显示了两个设置的默认值:
application.yml
eureka:
instance:
statusPageUrlPath: ${server.servletPath}/info
healthCheckUrlPath: ${server.servletPath}/health
这些链接显示在客户端使用的元数据中,并在某些情况下用于决定是否向你的应用程序发送请求,因此如果它们准确,则会很有帮助。
在 Dalston 中,还需要在更改管理上下文路径时设置状态和健康检查 URL,从 Edgware 开始删除此要求。
注册安全应用程序
如果你的应用程序想通过 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}。
如果你的应用程序在代理后面运行,并且 SSL 终止在代理中(例如,如果你在 Cloud Foundry 或其他平台中作为服务运行),然后,你需要确保代理“转发”headers 被应用程序拦截和处理。如果嵌入在 Spring Boot 应用程序中的 Tomcat 容器具有针对 X -Forwarded-\* headers 的显式配置,则会自动发生,应用程序呈现到自身的链接错误(错误的主机、端口或协议)表明你的配置错误。
Eureka 的健康检查
默认情况下,Eureka 使用客户端心跳来确定客户端是否已启动,除非另有说明,否则 Discovery Client 不会根据 Spring Boot Actuator 传播应用程序的当前健康检查状态,因此,在成功注册后,Eureka 始终宣布应用程序处于“UP”状态,通过启用 Eureka 健康检查可以更改此行为,从而将应用程序状态传播到 Eureka。因此,每个其他应用程序都不会向“UP”以外的状态下的应用程序发送流量,以下示例显示如何为客户端启用健康检查:
application.yml
eureka:
client:
healthcheck:
enabled: true
eureka.client.healthcheck.enabled=true 应该只在 application.yml 中设置,在 bootstrap.yml 中设置值会导致不良副作用,例如在 Eureka 中以 UNKNOWN 状态注册。
如果你需要更多控制健康检查,请考虑实现自己的 com.netflix.appinfo.HealthCheckHandler。
实例和客户端的 Eureka 元数据
值得花一些时间了解 Eureka 元数据的工作原理,因此你可以在平台中使用它,有用于信息的标准元数据,如主机名、IP 地址、端口号、状态页和健康检查。这些发布在服务注册表中,客户端使用它们以直接的方式联系服务,可以将额外元数据添加到 eureka.instance.metadataMap 中的实例注册中,并且可以在远程客户端中访问此元数据。通常,除非客户端了解元数据的含义,否则额外元数据不会更改客户端的行为,本文稍后将介绍几种特殊情况,其中 Spring Cloud 已经为元数据映射赋予了意义。
在 Cloud Foundry 上使用 Eureka
Cloud Foundry 有一个全局路由器,因此同一个应用程序的所有实例都具有相同的主机名(具有类似架构的其他 PaaS 解决方案),这不一定是使用 Eureka 的障碍。但是,如果你使用路由器(建议甚至强制使用,具体取决于你的平台的设置方式),你需要明确设置主机名和端口号(安全或非安全),以便他们使用路由器。你可能还希望使用实例元数据,以便区分客户端上的实例(例如,在自定义负载均衡器中),默认情况下,eureka.instance.instanceId 是 vcap.application.instance_id,如以下示例所示:
application.yml
eureka:
instance:
hostname: ${vcap.application.uris[0]}
nonSecurePort: 80
根据在 Cloud Foundry 实例中设置安全规则的方式,你可以注册并使用主机 VM 的 IP 地址进行直接服务到服务调用,Pivotal Web Services(PWS)尚未提供此功能。
在 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;
}
更改 Eureka 实例 ID
一个 vanilla 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 应用程序中,因此不需要随机值。
使用 EurekaClient
一旦你拥有一个发现客户端的应用程序,就可以使用它从 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(或者可能尚未启动 ApplicationContext 的任何地方),它在 SmartLifecycle 中初始化(phase=0),因此最早可以依赖它的是另一个具有更高阶段的 SmartLifecycle。
没有 Jersey 的 EurekaClient
默认情况下,EurekaClient 使用 Jersey 进行 HTTP 通信,如果你希望避免来自 Jersey 的依赖项,则可以将其从依赖项中排除,Spring Cloud 基于 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>
原生 Netflix EurekaClient 的替代品
你无需使用原始 Netflix EurekaClient,此外,在某种包装后面使用它通常更方便,Spring Cloud 通过逻辑 Eureka 服务标识符(VIP)而不是物理 URL 支持 Feign(REST 客户端构建器)和 Spring RestTemplate。要使用固定的物理服务器列表配置 Ribbon,可以将 <client>.ribbon.listOfServers 设置为以逗号分隔的物理地址(或主机名)列表,其中 <client> 是客户端的 ID。
你还可以使用 org.springframework.cloud.client.discovery.DiscoveryClient,它为发现客户端提供简单的 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;
}
为什么注册服务这么慢?
作为实例还涉及到注册表的定期心跳(通过客户端的 serviceUrl),默认持续时间为 30 秒,在实例、服务器和客户端在其本地缓存中都具有相同的元数据之前,客户端无法发现服务(因此可能需要 3 个心跳)。你可以通过设置 eureka.instance.leaseRenewalIntervalInSeconds 来更改周期,将其设置为小于 30 的值会加快使客户端连接到其他服务的过程,在生产中,最好坚持使用默认值,因为服务器中的内部计算会对租约续期做出假设。
Zones
如果你已将 Eureka 客户端部署到多个区域,你可能希望这些客户端在尝试另一个区域中的服务之前使用同一区域内的服务,要进行此设置,你需要正确配置 Eureka 客户端。
首先,你需要确保将 Eureka 服务器部署到每个区域,并确保它们彼此对等,有关详细信息,请参阅有关 Zones 和 Regions 的部分。
接下来,你需要告诉 Eureka 你的服务所在的区域,你可以使用 metadataMap 属性执行此操作,例如,如果将 service 1 部署到 zone 1 和 zone 2,则需要在 service 1 中设置以下 Eureka 属性:
zone 1 的 service 1
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true
zone 2 的 service 1
eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true