Spring Cloud Netflix 特性
服务发现:Eureka 客户端
服务发现是基于微服务架构的关键原则之一。尝试手动配置每个客户端或某种形式的约定可能难以实现且脆弱。Eureka 是 Netflix 的服务发现服务器和客户端。服务器可以配置和部署为高可用,每个服务器将注册服务的状态复制给其他服务器。
如何包含 Eureka 客户端
要在项目中包含 Eureka 客户端,请使用组 ID 为 org.springframework.cloud 且 artifact ID 为 spring-cloud-starter-netflix-eureka-client 的启动器。有关使用当前 Spring Cloud 发布列车设置构建系统的详细信息,请参阅 Spring Cloud 项目页面。
向 Eureka 注册
当客户端向 Eureka 注册时,它会提供自身的元数据——例如主机、端口、健康指示器 URL、主页和其他详细信息。Eureka 接收来自属于服务的每个实例的心跳消息。如果心跳在可配置的时间表内失败,则该实例通常会从注册表中移除。
以下示例显示了一个最小的 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: https://:8761/eureka/
在前面的示例中,defaultZone 是一个神奇的字符串回退值,它为任何未表达偏好(换句话说,它是一个有用的默认值)的客户端提供服务 URL。
defaultZone 属性区分大小写并要求使用驼峰命名法,因为 serviceUrl 属性是 Map<String, String>。因此,defaultZone 属性不遵循 default-zone 的常规 Spring Boot 蛇形命名约定。 |
默认应用程序名称(即服务 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),HTTP 基本身份验证会自动添加到您的 eureka 客户端。对于更复杂的需求,您可以创建 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 应用程序中有用端点的默认位置。即使对于 Actuator 应用程序,如果您使用非默认上下文路径或 servlet 路径(例如 server.servletPath=/custom),也需要更改这些设置。以下示例显示了这两个设置的默认值
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 或其他平台即服务中运行),那么您需要确保代理的“转发”头被应用程序截获和处理。如果 Spring Boot 应用程序中嵌入的 Tomcat 容器对“X-Forwarded-\*` 头有明确配置,这会自动发生。如果您的应用程序自身呈现的链接错误(主机、端口或协议错误),则表明您的配置有误。 |
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 注册(即,每个主机只有一个服务)。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}}}
通过前面示例中所示的元数据,以及部署在本地主机上的多个服务实例,随机值插入其中以使实例唯一。在 Cloud Foundry 中,vcap.application.instance_id 会在 Spring Boot 应用程序中自动填充,因此不需要随机值。
使用 EurekaClient
一旦您拥有一个作为发现客户端的应用程序,您就可以使用它从 Eureka 服务器发现服务实例。一种方法是使用原生的 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 支持 Feign(一个 REST 客户端构建器)和 Spring Cloud LoadBalancer,通过逻辑 Eureka 服务标识符(VIP)而不是物理 URL。
您还可以使用 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 属性
Zone 1 中的服务 1
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true
Zone 2 中的服务 1
eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true
刷新 Eureka 客户端
默认情况下,EurekaClient bean 是可刷新的,这意味着 Eureka 客户端属性可以更改和刷新。当发生刷新时,客户端将从 Eureka 服务器取消注册,并且可能会出现给定服务的所有实例都不可用的短暂时间。消除这种情况发生的一种方法是禁用刷新 Eureka 客户端的能力。为此,请设置 eureka.client.refresh.enable=false。
将 Eureka 与 Spring Cloud LoadBalancer 结合使用
我们支持 Spring Cloud LoadBalancer 的 ZonePreferenceServiceInstanceListSupplier。Eureka 实例元数据(eureka.instance.metadataMap.zone)中的 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 服务器,请使用组 ID 为 org.springframework.cloud 且 artifact ID 为 spring-cloud-starter-netflix-eureka-server 的启动器。有关使用当前 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);
}
}
服务器具有带 UI 的主页和用于 /eureka/* 下的正常 Eureka 功能的 HTTP API 端点。
以下链接提供了一些 Eureka 背景阅读材料:flux capacitor 和 google group discussion。
|
由于 Gradle 的依赖解析规则以及缺少父 bom 功能,依赖 build.gradle
|
defaultOpenForTrafficCount 及其对 EurekaServer 预热时间的影响
Spring Cloud Eureka 服务器在启动时不会考虑 Netflix Eureka 的 waitTimeInMsWhenSyncEmpty 设置。要启用预热时间,请设置 eureka.server.defaultOpenForTrafficCount=0。
高可用性、区域和地域
Eureka 服务器没有后端存储,但注册表中的服务实例都必须发送心跳以保持其注册最新(因此可以在内存中完成)。客户端也具有 Eureka 注册的内存缓存(因此它们不必为对服务的每个请求都访问注册表)。
默认情况下,每个 Eureka 服务器也是一个 Eureka 客户端,并且需要(至少一个)服务 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 添加到服务器的类路径中,通过 spring-boot-starter-security 来简单地保护您的 Eureka 服务器。默认情况下,当 Spring Security 在类路径中时,它会要求每个对应用程序的请求都发送有效的 CSRF 令牌。Eureka 客户端通常不具备有效的跨站点请求伪造 (CSRF) 令牌,因此您需要禁用 /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 示例 仓库 中找到一个演示 Eureka 服务器。
JDK 11 支持
Eureka 服务器依赖的 JAXB 模块在 JDK 11 中已被移除。如果您打算在运行 Eureka 服务器时使用 JDK 11,则必须在 POM 或 Gradle 文件中包含这些依赖项。
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
指标
EurekaInstanceMonitor 监听与 Eureka 实例注册相关的事件,并在 Micrometer 的 MeterRegistry 中创建/更新 Eureka 实例信息的 Gauge。默认情况下,此行为已禁用。如果您想启用它,您需要将 eureka.server.metrics.enabled 设置为 true。
默认情况下,Gauge 的名称为 eureka.server.instances 并具有以下标签
-
application:应用程序名称 -
status:实例状态(UP,DOWN,STARTING,OUT_OF_SERVICE,UNKNOWN,参见:com.netflix.appinfo.InstanceInfo.InstanceStatus)
您可以通过注入自己的 EurekaInstanceTagsProvider 实现来添加额外的标签。
配置属性
要查看所有 Spring Cloud Netflix 相关配置属性的列表,请查看附录页面。