Spring Cloud Netflix 功能
服务发现:Eureka 客户端
服务发现是微服务架构的关键原则之一。尝试手动配置每个客户端或采用某种约定可能很困难且脆弱。Eureka 是 Netflix 的服务发现服务器和客户端。服务器可以配置和部署为高可用,每个服务器将其注册服务的状态复制给其他服务器。
如何包含 Eureka 客户端
要在您的项目中包含 Eureka 客户端,请使用 group ID 为 org.springframework.cloud
、artifact ID 为 spring-cloud-starter-netflix-eureka-client
的启动器。有关如何使用当前 Spring Cloud Release Train 设置您的构建系统,请参阅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 应用。通过在 classpath 中包含 spring-cloud-starter-netflix-eureka-client
,您的应用会自动向 Eureka Server 注册。需要进行配置以定位 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}
。
在 classpath 中包含 spring-cloud-starter-netflix-eureka-client
会使应用既成为一个 Eureka“实例”(即它注册自己),又成为一个“客户端”(它可以查询注册中心来定位其他服务)。实例行为由 eureka.instance.*
配置键驱动,但如果您确保应用有 spring.application.name
的值(这是 Eureka 服务 ID 或 VIP 的默认值),则默认设置即可。
有关可配置选项的更多详细信息,请参阅 EurekaInstanceConfigBean 和 EurekaClientConfigBean。
要禁用 Eureka Discovery Client,可以将 eureka.client.enabled
设置为 false
。当 spring.cloud.discovery.enabled
设置为 false
时,Eureka Discovery Client 也将被禁用。
目前不支持将 Spring Cloud Netflix Eureka 服务器版本指定为路径参数。这意味着您无法在上下文路径 (eurekaServerURLContext ) 中设置版本。相反,您可以将版本包含在服务器 URL 中(例如,您可以设置 defaultZone: localhost:8761/eureka/v2 )。 |
向 Eureka Server 进行认证
如果 eureka.client.serviceUrl.defaultZone
的某个 URL 中嵌入了凭据(curl 风格,如下所示:user:password@localhost:8761/eureka
),则会自动为您的 Eureka 客户端添加 HTTP Basic 认证。对于更复杂的需求,您可以创建类型为 DiscoveryClientOptionalArgs
的 @Bean
,并将 ClientFilter
实例注入其中,所有这些都将应用于从客户端到服务器的调用。
当 Eureka server 需要客户端证书进行认证时,可以通过属性配置客户端证书和信任库,如下例所示
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 的限制,无法支持每个服务器的 Basic Auth 凭据,因此仅使用找到的第一组凭据。 |
如果您想自定义 Eureka HTTP 客户端使用的 RestTemplate,您可以考虑创建 EurekaClientHttpRequestFactorySupplier
类型的 bean,并提供自己的逻辑来生成 ClientHttpRequestFactory
实例。
Eureka HTTP 客户端使用的 RestTemplate 和 RestClient 的所有默认超时相关属性都设置为 3 分钟(与 Apache HC5 默认的 RequestConfig
和 SocketConfig
保持一致)。因此,要指定超时值,您必须直接通过 eureka.client.rest-template-timeout
或 eureka.client.restclient.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 使用客户端心跳来判断客户端是否处于 UP 状态。除非另有指定,否则 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
内部使用 RestClient
、RestTemplate
、WebClient
或 JerseyClient
。为了使用 EurekaClient
,您的 classpath 中需要包含其中一个受支持的 HTTP 客户端。
要使用 RestTemplate
或 RestClient
,请将 spring-boot-starter-web
添加到您的依赖项中。要使用 WebClient
,请将 spring-boot-starter-webflux
添加到您的依赖项中。如果 spring-boot-starter-web
和 spring-boot-starter-webflux
都包含在依赖项中并且 eureka.client.webclient.enabled
标志设置为 true
,则将使用 WebClient
。如果不是这种情况并且 eureka.client.restclient.enabled
设置为 true
,则将使用 RestClient
。否则,将使用 RestTemplate
。
对于这些客户端实现的任何一种,如果存在可用的 builder bean,则将使用它来创建底层客户端。 |
我们计划在下一个主要版本中将默认客户端更改为 RestClient 。 |
如果您希望改用 Jersey,您需要将 Jersey 依赖项添加到您的 classpath 中。以下示例显示了您需要添加的依赖项
<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>
如果您的 classpath 中包含 JerseyClient
但不想在 EuerekaClient
中使用它,请确保将 eureka.client.jersey.enabled
设置为 false
。
原生 Netflix EurekaClient 的替代方案
您不必使用原始的 Netflix EurekaClient
。此外,通常更方便使用某种包装器来使用它。Spring Cloud 通过逻辑的 Eureka 服务标识符(VIP)而不是物理 URL 来支持 Feign(一个 REST 客户端 builder)和 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 的值可以加快客户端连接到其他服务的速度。在生产环境中,由于服务器内部计算对续约周期的假设,最好还是坚持使用默认值。
区域(Zones)
如果您已将 Eureka 客户端部署到多个区域(zones),您可能希望这些客户端优先使用同一区域内的服务,然后再尝试其他区域的服务。要进行此设置,您需要正确配置您的 Eureka 客户端。
首先,您需要确保已将 Eureka 服务器部署到每个区域,并且它们相互之间是对等节点。有关更多信息,请参阅关于区域和地域(zones and 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
刷新 Eureka 客户端
默认情况下,EurekaClient
bean 是可刷新的,这意味着可以更改和刷新 Eureka 客户端属性。当发生刷新时,客户端将从 Eureka server 取消注册,并且可能存在给定服务的所有实例短暂不可用的情况。避免这种情况发生的一种方法是禁用刷新 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 Server
要在您的项目中包含 Eureka Server,请使用 group ID 为 org.springframework.cloud
、artifact ID 为 spring-cloud-starter-netflix-eureka-server
的启动器。有关如何使用当前 Spring Cloud Release Train 设置您的构建系统,请参阅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 群组讨论。
由于 Gradle 的依赖解析规则以及缺少 parent bom 功能,依赖于 build.gradle
|
defaultOpenForTrafficCount
及其对 EurekaServer 预热时间的影响
Netflix Eureka 的 waitTimeInMsWhenSyncEmpty
设置在 Spring Cloud Eureka server 启动初期并未被考虑。为了启用预热时间,请设置 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 Profile 下运行,将同一个服务器运行在两个主机(peer1
和 peer2
)上。您可以使用此配置通过修改 /etc/hosts
来解析主机名,从而在单个主机上测试对等感知(这在生产环境中价值不大)。事实上,如果您运行在一台知道自己主机名的机器上(默认情况下,它通过 java.net.InetAddress
查找),则不需要 eureka.instance.hostname
。
您可以在系统中添加多个对等节点,只要它们之间至少通过一条边相互连接,它们就会相互同步注册信息。如果对等节点物理上是分开的(在数据中心内部或多个数据中心之间),原则上,系统可以承受“脑裂”(split-brain)类型的故障。您可以在系统中添加多个对等节点,只要它们之间直接相互连接,它们就会相互同步注册信息。
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-boot-starter-security
将 Spring Security 添加到服务器的 classpath 中,即可保护您的 Eureka 服务器。默认情况下,当 classpath 中存在 Spring Security 时,它会要求每个发送到应用程序的请求都附带有效的 CSRF token。Eureka 客户端通常不具备有效的跨站请求伪造 (CSRF) token,因此您需要对 /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 Samples 仓库中找到一个 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 相关配置属性的列表,请查看附录页面。