Spring Cloud Netflix 特性
服务发现:Eureka 客户端
服务发现是微服务架构中的关键原则之一。尝试手动配置每个客户端或某种约定可能很难实现,且容易出现脆弱性问题。Eureka 是 Netflix 的服务发现服务器和客户端。该服务器可配置并部署为高可用状态,每个服务器会将注册服务的状态信息复制到其他服务器上。
如何包含 Eureka 客户端
要在您的项目中包含 Eureka 客户端,请使用组 ID 为 org.springframework.cloud、构件 ID 为 spring-cloud-starter-netflix-eureka-client 的Starters。Spring Cloud 项目页面 可提供有关如何使用当前 Spring Cloud 发行版列车配置构建系统的详细信息。
注册到 Eureka
当客户端向 Eureka 注册时,它会提供有关自身的元数据——例如主机、端口、健康指标 URL、主页及其他详细信息。</p><p>Eureka 会从属于某个服务的每个实例接收心跳消息。</p><p>如果在可配置的时间表内心跳失败,则该实例通常会被从注册表中移除。
以下示例展示了一个最小的 Eureka 客户端应用程序:
@SpringBootApplication
@RestController
public class Application {
@RequestMapping("/")
public String home() {
return "Hello world";
}
public static void main(String[] args) {
SpringApplication.run(CustomerServiceTestApplication.class, args);
}
}
注意,前面的示例展示了一个标准的 Spring Boot 应用程序。
由于类路径中包含 spring-cloud-starter-netflix-eureka-client,您的应用程序会自动向 Eureka 服务器注册。需要进行配置以定位 Eureka 服务器,如以下示例所示:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
在前面的示例中,defaultZone 是一个魔术字符串备用值,它为任何未表达偏好的客户端(即,这是一个有用的默认值)提供服务 URL。
属性 defaultZone 区分大小写,且必须使用驼峰命名法,因为 serviceUrl 属性是一个 Map<String, String>。因此,defaultZone 属性不符合 Spring Boot 默认的蛇形命名规范(即 default-zone)。 |
默认的应用名称(即服务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 发现客户端,可将 eureka.client.enabled 设置为 false。当 spring.cloud.discovery.enabled 设置为 false 时,Eureka 发现客户端也会被禁用。
将 Spring Cloud Netflix Eureka 服务器的版本指定为路径参数目前不被支持。这意味着您无法在上下文路径中设置版本(eurekaServerURLContext)。相反,您可以将版本包含在服务器 URL 中(例如,您可以设置 defaultZone: localhost:8761/eureka/v2)。 |
与 Eureka 服务器进行身份验证
如果其中一个 eureka.client.serviceUrl.defaultZone URL 中嵌入了凭据(如 curl 风格所示:user:password@localhost:8761/eureka),则会自动为您的 Eureka 客户端添加 HTTP 基本认证。对于更复杂的需要,您可以创建一个类型为 DiscoveryClientOptionalArgs 的 @Bean,并将 ClientFilter 实例注入其中,所有这些都将应用于客户端到服务器的调用。
当 Eureka 服务器需要客户端证书进行身份验证时,可以通过属性配置客户端证书和信任库,如下例所示:
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-type 和 eureka.client.tls.trust-store-type 是 PKCS12。当密码属性被省略时,默认假设为空密码。
| 由于 Eureka 的限制,无法支持每台服务器的基本身份验证凭据,因此仅使用找到的第一组凭据。 |
如果您想要自定义 Eureka HTTP 客户端所使用的 RestClient,可以创建一个 EurekaClientHttpRequestFactorySupplier 类型的 Bean,并提供自定义逻辑以生成 ClientHttpRequestFactory 实例。
所有由 Eureka HTTP 客户端使用的默认超时相关属性(RestClient)均设置为 3 分钟(与 Apache HC5 默认值 RequestConfig 和 SocketConfig 保持一致)。因此,若要指定超时值,您必须直接通过 eureka.client.timeout 中的属性来设定。所有超时属性的单位均为毫秒。
eureka:
client:
restclient:
timeout:
connect-timeout: 5000
connect-request-timeout: 8000
socket-timeout: 10000
rest-template-timeout:
connect-timeout: 5000
connect-request-timeout: 8000
socket-timeout: 10000
你还可以通过创建类型为 EurekaClientHttpRequestFactorySupplier.RequestConfigCustomizer 的 Bean 来自定义底层 Apache HttpClient 5 的 RequestConfig:
@Configuration
public class RestClientConfiguration {
@Bean
EurekaClientHttpRequestFactorySupplier.RequestConfigCustomizer requestConfigCustomizer() {
return builder -> builder.setProtocolUpgradeEnabled(false);
}
}
状态页面和健康指标
Eureka 实例的状态页面和健康指标默认值分别为 /info 和 /health,这两个值是 Spring Boot Actuator 应用中常用端点的默认位置。如果您使用非默认的上下文路径或 Servlet 路径(例如 server.servletPath=/custom),即使是在 Actuator 应用中,也必须对其进行更改。以下示例展示了这两个设置的默认值:
eureka:
instance:
statusPageUrlPath: ${server.servletPath}/info
healthCheckUrlPath: ${server.servletPath}/health
这些链接会出现在客户端所使用的元数据中,并在某些场景下用于决定是否向您的应用程序发送请求,因此如果它们准确无误将非常有帮助。
| 在 Dalston 版本中,当更改管理上下文路径时,也必须设置状态和健康检查 URL。此要求自 Edgware 版本起已被移除。 |
注册安全应用程序
如果您的应用程序希望通过 HTTPS 进行通信,您可以在 EurekaInstanceConfigBean 中设置两个标志:
-
eureka.instance.[nonSecurePortEnabled]=[false] -
eureka.instance.[securePortEnabled]=[true]
这样做会使 Eureka 发布实例信息,以显示对安全通信的明确偏好。
Spring Cloud DiscoveryClient 始终为按此方式配置的服务返回一个以 https 开头的 URI。
同样,当服务按此方式配置时,Eureka(原生)实例信息中会包含一个安全的健康检查 URL。
由于 Eureka 内部的工作方式,它仍然会发布一个非安全的 URL 来提供状态页和主页,除非您也显式地覆盖这些页面。您可以使用占位符来配置 Eureka 实例的 URL,如下所示示例:
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 或其他平台即服务环境中运行),则需要确保代理的“转发”头信息被应用程序捕获并处理。</p><p>如果嵌入在 Spring Boot 应用程序中的 Tomcat 容器对 'X-Forwarded-*' 头部有显式配置,则此操作会自动完成。</p><p>您应用程序自身生成的链接出现错误(如主机名、端口或协议不正确)表明该配置存在错误。 |
Eureka的健康检查
默认情况下,Eureka 使用客户端心跳来判断客户端是否处于活跃状态。除非另有指定,Discovery Client 不会根据 Spring Boot Actuator 的当前健康检查状态传播应用的健康状况。因此,在成功注册后,Eureka 始终将应用状态宣告为“UP”(运行中)。通过启用 Eureka 健康检查,可以改变此行为,从而将应用的状态传播至 Eureka。结果是,其他所有应用仅向状态为“UP”的应用发送流量。以下示例展示了如何为客户端启用健康检查:
eureka:
client:
healthcheck:
enabled: true
eureka.client.healthcheck.enabled=true 仅应在 application.yml 中设置。在 bootstrap.yml 中设置该值会导致不良副作用,例如以 UNKNOWN 状态在 Eureka 中注册。 |
如果您需要对健康检查拥有更多控制权,可考虑实现自己的 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,如以下示例所示:
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 bean = new EurekaInstanceConfigBean(inetUtils);
AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
bean.setDataCenterInfo(info);
return bean;
}
更改 Eureka 实例 ID
一个原生的 Netflix Eureka 实例以与其主机名相同的 ID 进行注册(即每台主机上仅有一个服务)。</p><p>Spring Cloud Eureka 提供了一个合理的默认配置,其定义如下:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
一个例子是 myhost:myappname:8080。
通过使用 Spring Cloud,您可以通过提供一个唯一标识符来覆盖此值,如以下示例所示:eureka.instance.instanceId。
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,因此无需使用随机值。
使用 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();
}
|
不要在 |
底层 HTTP 客户端
EurekaClient 使用 RestClient、WebClient 或 JerseyClient 作为底层实现。若要使用 EurekaClient,您需要在类路径中包含至少一个受支持的 HTTP 客户端。
要使用 RestClient,请将 spring-boot-restclient 添加到您的依赖项中。要使用 WebClient,请将 spring-boot-webclient 添加到您的依赖项中。如果依赖项中同时包含 spring-boot-restclient 和 spring-boot-webclient,且 eureka.client.webclient.enabled 标志设置为 true,则将使用 WebClient。否则,将使用 RestClient。
| 对于这些客户端实现中的任何一种,如果存在构建器Bean,则将使用它来创建底层客户端。 |
我们计划在下一个主要版本中将默认客户端更改为 RestClient。 |
如果您希望使用 Jersey 替代方案,则需要将 Jersey 依赖项添加到您的类路径中。以下示例展示了您需要添加的依赖项:
<dependencies>
<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>
</dependencies>
如果您在类路径中包含 JerseyClient,但不希望在您的 EuerekaClient 中使用它,请确保将 eureka.client.jersey.enabled 设置为 false。
原生 Netflix EurekaClient 的替代方案
您无需直接使用原始的 Netflix EurekaClient。此外,通常更方便的是将其置于某种包装器之后使用。Spring Cloud 通过逻辑 Eureka 服务标识符(VIPs)而非物理 URL,为 Feign(一个 REST 客户端构建器)和 Spring Cloud LoadBalancer 提供了支持。
您还可以使用 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 的值可加快客户端连接到其他服务的速度。在生产环境中,建议仍使用默认值,因为服务器内部存在一些计算,这些计算依赖于租约续期时间的假设。
区域
如果您已将 Eureka 客户端部署到多个可用区,您可能更希望这些客户端优先使用同一可用区内的服务,然后再尝试其他可用区中的服务。要实现这一点,您需要正确配置您的 Eureka 客户端。
首先,您需要确保每个可用区都部署了 Eureka 服务器,并且它们相互为对等节点。
有关更多信息,请参阅 可用区和区域 部分。
接下来,您需要告诉 Eureka 您的服务位于哪个区域。您可以通过使用 metadataMap 属性来实现此目的。例如,如果 service 1 部署在 zone 1 和 zone 2 中,则您需要在 service 1 中设置以下 Eureka 属性:
服务 1 在区域 1
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true
服务 1 在区域 2
eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true
刷新 Eureka 客户端
默认情况下,EurekaClient 个 Bean 是可刷新的,这意味着 Eureka 客户端属性可以被更改并刷新。当发生刷新时,客户端将从 Eureka 服务器上取消注册,并可能有一段短暂的时间,在此期间,给定服务的所有实例均不可用。消除这种情况的一种方法是禁用刷新 Eureka 客户端的功能。为此,请设置 eureka.client.refresh.enable=false。
使用 Eureka 与 Spring Cloud 负载均衡
我们提供对 Spring Cloud LoadBalancer ZonePreferenceServiceInstanceListSupplier 的支持。Eureka 实例元数据中的 zone 值(eureka.instance.metadataMap.zone)用于设置 spring-cloud-loadbalancer-zone 属性的值,该属性用于按区域过滤服务实例。
如果缺失且设置了 spring.cloud.loadbalancer.eureka.approximateZoneFromHostname 标志为 true,它可使用服务器主机名中的域名作为区域的代理。
如果没有其他区域数据来源,则根据客户端配置(而非实例配置)进行猜测。我们取 eureka.client.availabilityZones,这是一个从区域名称到区域列表的映射,并提取实例所在区域的第一个区域(即 eureka.client.region,其默认值为 "us-east-1",以保持与原生 Netflix 的兼容性)。
服务发现:Eureka 服务器
本节介绍如何设置 Eureka 服务器。
如何包含 Eureka 服务器
要在您的项目中包含 Eureka Server,请使用组 ID 为 org.springframework.cloud、构件 ID 为 spring-cloud-starter-netflix-eureka-server 的Starters。有关如何使用当前 Spring Cloud 发行版系列配置构建系统的详细信息,请参阅 Spring Cloud 项目页面。
| 如果您的项目已经使用 Thymeleaf 作为模板引擎,则 Eureka 服务器的 Freemarker 模板可能无法正确加载。此时,有必要手动配置模板加载器: |
spring:
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
如何运行 Eureka 服务器
以下示例展示了一个最小化的 Eureka 服务器:
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(CustomerServiceTestApplication.class, args);
}
}
服务器具有一个主页,包含用户界面和 HTTP API 端点,用于提供常规的 Eureka 功能,路径为 /eureka/*。
以下链接提供了一些关于 Eureka 的背景资料:通量稳定器 和 Google 群组讨论。
|
由于 Gradle 的依赖解析规则以及缺乏父 BOM(Bill of Materials)功能,依赖 build.gradle
|
defaultOpenForTrafficCount及其对 EurekaServer 预热时间的影响
Netflix Eureka 的 waitTimeInMsWhenSyncEmpty 设置在 Spring Cloud Eureka 服务器启动初期未被采纳。为启用预热时间,请设置 eureka.server.defaultOpenForTrafficCount=0。
高可用性、区域和区
Eureka 服务器没有后端存储,但注册表中的所有服务实例都必须发送心跳以保持其注册信息的最新(因此可以内存中完成)。</p><p>客户端也维护着 Eureka 注册信息的内存缓存(因此它们无需在每次请求服务时都去查询注册表)。
默认情况下,每个 Eureka 服务器同时也是 Eureka 客户端,并且至少需要提供一个服务 URL 才能定位对等节点。如果您未提供该 URL,服务仍会运行并正常工作,但会向您的日志中输出大量关于无法注册到对等节点的噪声信息。
独立模式
客户端缓存和服务器端缓存的结合,以及心跳机制,使得独立运行的 Eureka 服务器在出现故障时仍具有相当强的容错能力,只要存在某种监控或弹性运行时环境(例如 Cloud Foundry)来维持其运行即可。在独立模式下,您可能更倾向于关闭客户端行为,以避免其持续尝试连接其对等节点却不断失败。以下示例展示了如何关闭客户端行为:
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
注意,serviceUrl 指向的主机与本地实例相同。
同伴意识
Eureka 可以通过运行多个实例并要求它们相互注册,从而变得更加健壮和可用。实际上,这是默认行为,因此您只需向一个对等节点添加一个有效的 serviceUrl 即可使其正常工作,如下例所示:
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: https://peer2/eureka/
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: https://peer1/eureka/
在前面的例子中,我们有一个 YAML 文件,可以通过在不同的 Spring 配置文件中运行它,使同一服务器在两个主机(peer1 和 peer2)上运行。您可利用此配置在单台主机上测试对等节点感知能力(在生产环境中这样做价值不大),方法是将 /etc/hosts 修改为解析主机名。事实上,如果您在一台能够识别自身主机名的机器上运行(默认情况下,它是通过 java.net.InetAddress 查找的),则 eureka.instance.hostname 并非必需。
您可以向系统中添加多个对等节点,只要它们彼此之间至少通过一条边相互连接,即可在彼此之间同步注册信息。如果这些对等节点在物理上相互分离(例如位于同一数据中心内或分布在多个数据中心之间),那么该系统原则上能够应对‘脑裂’类型的故障。您可以向系统中添加多个对等节点,只要它们彼此直接相连,即可在彼此之间同步注册信息。
eureka:
client:
serviceUrl:
defaultZone: https://peer1/eureka/,http://peer2/eureka/,http://peer3/eureka/
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
---
spring:
profiles: peer3
eureka:
instance:
hostname: peer3
何时优先选择IP地址
在某些情况下,更倾向于让 Eureka 宣告服务的 IP 地址而非主机名。
将 eureka.instance.preferIpAddress 设置为 true,当应用程序向 Eureka 注册时,它将使用其 IP 地址而非主机名。
|
如果 Java 无法确定主机名,则将 IP 地址发送至 Eureka。 |
保护 Eureka 服务器
您只需通过在服务器的类路径中添加 Spring Security 即可保护您的 Eureka 服务器,方法是使用 spring-boot-starter-security。默认情况下,当 Spring Security 在类路径中时,它将要求每个请求都必须包含有效的 CSRF Tokens。Eureka 客户端通常不会拥有有效的跨站请求伪造(CSRF)Tokens,因此您需要为 /eureka/** 端点禁用此要求。例如:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated())
.httpBasic(withDefaults());
http.csrf().ignoringRequestMatchers("/eureka/**");
return http.build();
}
有关CSRF的更多信息,请参阅Spring Security文档。
在 Spring Cloud 示例仓库 repo 中可找到一个 Eureka Server 的演示实例。
JDK 11 支持
由于 JDK 11 中已移除 Eureka 服务器所依赖的 JAXB 模块,如果您计划在运行 Eureka 服务器时使用 JDK 11,则必须在您的 POM 文件或 Gradle 文件中添加这些依赖项。
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
指标
EurekaInstanceMonitor 监听与 Eureka 实例注册相关的事件,并创建/更新 Gauge`s for Eureka instance information in Micrometer’s `MeterRegistry。默认情况下,此行为是禁用的。如果您希望启用它,需要将 eureka.server.metrics.enabled 设置为 true。
默认情况下,Gauge`s are named `eureka.server.instances 和 具有以下标签:
-
application: 应用名称 -
status: 实例状态 (UP,DOWN,STARTING,OUT_OF_SERVICE,UNKNOWN, 详见:com.netflix.appinfo.InstanceInfo.InstanceStatus)
您可以通过注入自己的 EurekaInstanceTagsProvider 实现来添加额外的标签。
配置属性
要查看所有与 Spring Cloud Netflix 相关的配置属性列表,请参阅 附录页面。