Spring Cloud Netflix 功能
服务发现:Eureka 客户端
服务发现是基于微服务的架构的关键原则之一。尝试手动配置每个客户端或某种形式的约定可能难以实现,并且可能很脆弱。Eureka 是 Netflix 的服务发现服务器和客户端。可以配置并部署服务器以实现高可用性,每个服务器将有关已注册服务的元数据复制到其他服务器。
如何包含 Eureka 客户端
要在项目中包含 Eureka 客户端,请使用组 ID 为 org.springframework.cloud
且构件 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) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
请注意,前面的示例显示了一个正常的 Spring Boot 应用程序。通过在类路径上包含 spring-cloud-starter-netflix-eureka-client
,您的应用程序会自动向 Eureka 服务器注册。需要进行配置才能找到 Eureka 服务器,如以下示例所示
eureka: client: serviceUrl: defaultZone: https://127.0.0.1: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 客户端使用的 RestTemplate,则可能需要创建 EurekaClientHttpRequestFactorySupplier
的 bean 并提供您自己的逻辑来生成 ClientHttpRequestFactory
实例。
Eureka HTTP 客户端使用的 RestTemplate 所有与超时相关的默认属性都设置为 3 分钟(与 Apache HC5 默认 RequestConfig
和 SocketConfig
保持一致)。因此,要指定超时值,必须使用 eureka.client.rest-template-timeout
中的属性直接指定值。(所有超时属性均以毫秒为单位。)
eureka:
client:
rest-template-timeout:
connect-timeout: 5000
connect-request-timeout: 8000
socket-timeout: 10000
状态页面和健康指示器
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,否则它仍然会发布状态和主页的非安全 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}}}
使用前面示例中显示的元数据并在 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(); }
不要在 |
底层 HTTP 客户端
EurekaClient
在后台使用 RestTemplate
、WebClient
或 JerseyClient
。为了使用 EurekaClient
,您需要在类路径中包含一个受支持的 HTTP 客户端。
要使用 RestTemplate
,请将 spring-boot-starter-web
添加到您的依赖项中。要使用 WebClient
,请将 spring-boot-starter-webflux
添加到您的依赖项中。如果当 eureka.client.webclient.enabled
设置为 true
时,RestTemplate
和 WebClient
都在类路径中,则使用 WebClient
。否则,将使用 RestTemplate
。
如果您希望使用 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 属性
区域 1 中的服务 1
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true
区域 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` 且构件 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) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
服务器有一个带有 UI 的主页和用于 Eureka 常规功能的 HTTP API 端点,位于 `/eureka/*` 下。
以下链接提供了一些 Eureka 背景阅读:flux capacitor 和 google group discussion。
由于 Gradle 的依赖项解析规则以及缺少父 BOM 功能,依赖 `spring-cloud-starter-netflix-eureka-server` 可能会导致应用程序启动时失败。要解决此问题,请添加 Spring Boot Gradle 插件并导入 Spring Cloud 启动器父 BOM,如下所示。 build.gradle
|
defaultOpenForTrafficCount
及其对 Eureka 服务器预热时间的影响
Netflix Eureka 的 `waitTimeInMsWhenSyncEmpty` 设置在 Spring Cloud Eureka 服务器启动时未被考虑在内。为了启用预热时间,请设置 `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` 以解析主机名。实际上,如果您在知道自身主机名的机器上运行,则不需要 `eureka.instance.hostname`(默认情况下,它通过使用 `java.net.InetAddress` 进行查找)。
您可以向系统添加多个对等体,并且只要它们都通过至少一个边缘相互连接,它们就会在彼此之间同步注册信息。如果对等体在物理上分离(在数据中心内部或多个数据中心之间),则原则上系统可以承受“脑裂”类型的故障。您可以向系统添加多个对等体,并且只要它们都直接相互连接,它们就会在彼此之间同步注册信息。
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.instance.hostname` 属性。您可以使用环境变量在运行时设置主机名,例如 `eureka.instance.hostname=${HOST_NAME}`。 |
保护 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 示例 repo 中找到一个演示 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 相关的配置属性列表,请查看 附录页面。