# Spring Cloud Netflix
3.1.1
该项目通过自动配置和绑定到 Spring 环境和其他 Spring 编程模型习惯用法,为 Spring 引导应用程序提供 Netflix OSS 集成。通过一些简单的注释,你可以快速启用和配置应用程序中的常见模式,并使用经过战斗测试的 Netflix 组件构建大型分布式系统。提供的模式包括服务发现。
# 1.服务发现:Eureka客户端
服务发现是基于微服务的体系结构的关键原则之一。尝试手动配置每个客户机或某种形式的约定可能很难做到,并且可能很脆弱。Eureka 是 Netflix 的服务发现服务器和客户端。可以将服务器配置和部署为高度可用,每个服务器都将有关已注册服务的状态复制到其他服务器。
# 1.1.如何包含 Eureka 客户端
要在项目中包含 Eureka 客户机,请使用组 ID 为org.springframework.cloud
和工件 ID 为spring-cloud-starter-netflix-eureka-client
的 starter。请参阅Spring Cloud Project page (opens new window),以获取有关使用当前 Spring Cloud发布系列设置构建系统的详细信息。
# 1.2.在 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 (opens new window)应用程序。通过在 Classpath 上具有spring-cloud-starter-netflix-eureka-client
,你的应用程序将自动向 Eureka 服务器注册。需要配置来定位 Eureka 服务器,如以下示例所示:
应用程序.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
在前面的示例中,defaultZone
是一个 magic string fallback 值,它为不表示偏好的任何客户机提供服务 URL(换句话说,它是一个有用的默认值)。
defaultZone 属性是区分大小写的,并且需要大小写,因为serviceUrl 属性是Map<String, String> 。因此,defaultZone 属性不遵循正常的 Spring 引导 snake-case 约定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 的默认值),那么默认值就可以了。
有关可配置选项的更多详细信息,请参见Eurekainstanconfigbean (opens new window)和EurekaclientConfigBean (opens new window)。
要禁用 Eureka 发现客户端,可以将eureka.client.enabled
设置为false
。当spring.cloud.discovery.enabled
被设置为false
时,Eureka 发现客户端也将被禁用。
# 1.3.使用 Eureka 服务器进行身份验证
如果eureka.client.serviceUrl.defaultZone
URL 中有一个内嵌凭据(curl 样式,如下所示:[user:[email protected]:8761/eureka](https://user:password@localhost:8761/eureka)
),则 HTTP Basic 身份验证将自动添加到 Eureka 客户机。对于更复杂的需求,可以创建@Bean
类型的DiscoveryClientOptionalArgs
,并将ClientFilter
实例注入其中,所有这些都应用于从客户机到服务器的调用。
当 Eureka 服务器需要客户端证书来进行身份验证时,可以通过属性来配置客户端证书和信任存储区,如以下示例所示:
应用程序.yml
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
实例提供你自己的逻辑。
# 1.4.状态页和健康指标
Eureka 实例的状态页和健康指示器分别默认为/info
和/health
,这是 Spring 引导执行器应用程序中有用端点的默认位置。你需要更改这些,即使对于执行器应用程序,如果你使用非默认的上下文路径或 Servlet 路径(例如server.servletPath=/custom
),也需要更改这些内容。下面的示例显示了这两个设置的默认值:
应用程序.yml
eureka:
instance:
statusPageUrlPath: ${server.servletPath}/info
healthCheckUrlPath: ${server.servletPath}/health
这些链接会显示在客户使用的元数据中,并在某些场景中用于决定是否将请求发送到应用程序,因此如果它们是准确的,则会很有帮助。
在 Dalston 中,当更改 管理上下文路径时,还需要设置状态和健康检查 URL。这一要求从 Edgware 开始就被删除了。 |
---|
# 1.5.注册安全应用程序
如果你的应用程序希望通过 HTTPS 进行联系,则可以在EurekaInstanceConfigBean
中设置两个标志:
eureka.instance.[nonSecurePortEnabled]=[false]
eureka.instance.[securePortEnabled]=[true]
这样做使得 Eureka 发布实例信息,显示出对安全通信的明确偏好。 Spring 对于以这种方式配置的服务,云DiscoveryClient
总是返回一个以https
开头的 URI。类似地,当以这种方式配置服务时,Eureka(本地)实例信息具有安全的健康检查 URL。
由于 Eureka 的内部工作方式,它仍然为状态和主页发布一个非安全的 URL,除非你也显式地覆盖这些 URL。你可以使用占位符来配置 Eureka 实例 URL,如下例所示:
应用程序.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 或其他平台即服务中运行),然后,你需要确保代理“转发”头文件被应用程序拦截并处理。 如果嵌入在 Spring 启动应用程序中的 Tomcat 容器对“X-forwarded-\*”头文件有明确的配置,则会自动发生这种情况。 应用程序向自身呈现的链接是错误的(错误的主机,Port 或 Protocol)是你错误配置的标志。 |
---|
# 1.6. Eureka 的健康检查
默认情况下,Eureka 使用客户端心跳来确定客户端是否启动。除非另有指定,否则发现客户端不会根据 Spring 引导执行器传播应用程序的当前健康检查状态。因此,在成功注册之后,Eureka 总是宣布该应用程序处于“启动”状态。可以通过启用 Eureka 健康检查来改变此行为,从而将应用程序状态传播到 Eureka。因此,其他所有应用程序都不会将流量发送到其他“向上”状态的应用程序。下面的示例展示了如何为客户机启用健康检查:
应用程序.yml
eureka:
client:
healthcheck:
enabled: true
eureka.client.healthcheck.enabled=true 应该只设置在应用程序.yml 中。在bootstrap.yml 中设置该值会导致不良的副作用,例如在 Eureka 中使用UNKNOWN 状态注册。 |
---|
如果你需要对健康检查进行更多的控制,请考虑实现你自己的com.netflix.appinfo.HealthCheckHandler
。
# 1.7.用于实例和客户机的 Eureka 元数据
值得花一些时间来了解 Eureka 元数据是如何工作的,这样你就可以以一种在你的平台中有意义的方式使用它。对于诸如主机名、IP 地址、端口号、状态页和健康检查等信息,有标准的元数据。它们发布在服务注册中心中,并由客户机使用,以一种简单的方式与服务联系。可以在eureka.instance.metadataMap
中将其他元数据添加到实例注册中,并且可以在远程客户端中访问此元数据。通常,附加的元数据不会改变客户机的行为,除非使客户机意识到元数据的含义。有几个特殊情况(在本文后面描述),其中 Spring Cloud 已经为元数据映射分配了含义。
# 1.7.1.在 Cloud Foundry 上使用 Eureka
Cloud Foundry 有一个全球路由器,因此同一应用程序的所有实例都具有相同的主机名(具有类似架构的其他 PaaS 解决方案具有相同的安排)。这不一定是使用 Eureka 的障碍。但是,如果你使用路由器(根据你的平台设置方式,推荐甚至强制使用),则需要显式地设置主机名和端口号(安全或非安全),以便它们使用路由器。你可能还希望使用实例元数据,以便能够区分客户机上的实例(例如,在自定义负载均衡器中)。默认情况下,eureka.instance.instanceId
是vcap.application.instance_id
,如下例所示:
应用程序.yml
eureka:
instance:
hostname: ${vcap.application.uris[0]}
nonSecurePort: 80
根据在 Cloud Foundry 实例中设置安全规则的方式,你可能能够注册并使用主机 VM 的 IP 地址进行直接的服务到服务调用。此功能在 Pivotal Web 服务上还不可用(PWS (opens new window))。
# 1.7.2.在 AWS 上使用 Eureka
如果计划将应用程序部署到 AWS 云中,那么必须将 Eureka 实例配置为 AWS 感知的。你可以通过如下方式定制Eurekainstanconfigbean (opens new window)来实现此目的:
@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;
}
# 1.7.3.更改 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
中提供唯一的标识符来覆盖该值,如下例所示:
应用程序.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 引导应用程序中自动填充,因此不需要随机值。
# 1.8.使用 Eurekaclient
一旦你拥有了一个作为发现客户机的应用程序,就可以使用它从 Eureka 服务器中发现服务实例。这样做的一种方法是使用本机com.netflix.discovery.EurekaClient
(而不是 Spring CloudDiscoveryClient
),如以下示例所示:
@Autowired
private EurekaClient discoveryClient;
public String serviceUrl() {
InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false);
return instance.getHomePageUrl();
}
不要在@PostConstruct 方法或@Scheduled 方法(或尚未启动ApplicationContext 的任何地方)中使用EurekaClient 。它是在 SmartLifecycle 中初始化的(带有phase=0 ),因此,最早可以依赖它的是在另一个具有更高相位的SmartLifecycle 中。 |
---|
# 1.8.1.穿着球衣的 Eurekaclient
默认情况下,Eurekaclient 使用 Spring 的RestTemplate
进行 HTTP 通信。如果希望使用 Jersey,则需要将 Jersey 依赖项添加到 Classpath 中。下面的示例显示了你需要添加的依赖关系:
<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>
# 1.9.原生 Netflix Eurekaclient 的替代品
你不需要使用原始的 NetflixEurekaClient
。而且,通常在某种包装纸后面使用它会更方便。 Spring Cloud通过逻辑 Eureka 服务标识符(VIPS)而不是物理 URL 支持(REST 客户端构建器)和[[[ Spring ](# Spring-cloud-ribbon)。
你也可以使用org.springframework.cloud.client.discovery.DiscoveryClient
,它为 Discovery 客户端提供了一个简单的 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;
}
# 1.10.为什么注册服务这么慢?
作为一个实例,注册中心还需要一个周期性的心跳(通过客户机的serviceUrl
),默认持续时间为 30 秒。在实例、服务器和客户机的本地缓存中都有相同的元数据(因此可能需要 3 次心跳)之前,客户端无法发现服务。你可以通过设置eureka.instance.leaseRenewalIntervalInSeconds
来更改句号。将它设置为小于 30 的值可以加快客户连接到其他服务的过程。在生产中,最好坚持使用默认值,因为服务器中的内部计算对租赁续约期进行了假设。
# 1.11.区域
如果你已经将 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
# 1.12.刷新 Eureka 客户端
默认情况下,EurekaClient
Bean 是可刷新的,这意味着可以更改和刷新 Eureka 客户机属性。当刷新发生时,客户端将从 Eureka 服务器上被取消注册,并且可能会有一个短暂的时刻,其中给定服务的所有实例都不可用。避免这种情况发生的一种方法是禁用刷新 Eureka 客户机的功能。要执行此设置eureka.client.refresh.enable=false
。
# 1.13. Using Eureka with Spring Cloud LoadBalancer
我们为 Spring Cloud LoadBalancerZonePreferenceServiceInstanceListSupplier
提供支持。来自 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)拉出第一个区域。
# 2.服务发现:Eureka 服务器
本节介绍如何设置 Eureka 服务器。
# 2.1.如何包含 Eureka 服务器
要在项目中包含 Eureka 服务器,请使用组 ID 为org.springframework.cloud
和工件 ID 为spring-cloud-starter-netflix-eureka-server
的 starter。请参阅Spring Cloud Project page (opens new window)以获取有关使用当前 Spring Cloud发布列设置构建系统的详细信息。
如果你的项目已经使用 ThymeLeaf 作为其模板引擎,那么 Eureka 服务器的 Freemarker 模板可能无法正确加载。在这种情况下,需要手动配置模板加载器: |
---|
application.yml
spring:
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
# 2.2.如何运行 Eureka 服务器
下面的示例展示了一个最小的 Eureka 服务器:
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
该服务器有一个主页,在/eureka/*
下具有正常 Eureka 功能的 UI 和 HTTP API 端点。
以下链接有一些 Eureka 背景读数:磁通电容器 (opens new window)和谷歌小组讨论 (opens new window)。
由于 Gradle 的依赖关系解析规则和缺乏父 BOM 功能,依赖于spring-cloud-starter-netflix-eureka-server 可能会导致应用程序启动失败。要解决此问题,请添加 Spring boot Gradle 插件并按以下方式导入 Spring Cloud启动父 BOM: build. Gradle <br/>buildscript {<br/> dependencies {<br/> classpath("org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-docs-version}")<br/> }<br/>}<br/><br/>apply plugin: "spring-boot"<br/><br/>dependencyManagement {<br/> imports {<br/> mavenBom "org.springframework.cloud:spring-cloud-dependencies:{spring-cloud-version}"<br/> }<br/>}<br/> |
---|
# 2.3.高可用性、区域和区域
Eureka 服务器没有后端存储,但是注册表中的服务实例都必须发送心跳以保持其注册的最新状态(因此可以在内存中完成)。客户机还具有 Eureka 注册的内存缓存(因此,对于服务的每一个请求,客户机都不需要访问注册表)。
默认情况下,每个 Eureka 服务器也是一个 Eureka 客户机,并且需要(至少一个)服务 URL 来定位对等方。如果你不提供它,那么该服务就会运行并正常工作,但是它会在你的日志中充满大量关于无法向对等方注册的噪音。
# 2.4.独立模式
这两个缓存(客户机和服务器)和心跳的结合,使得独立的 Eureka 服务器能够很好地抵御故障,只要有某种监视器或弹性运行时(例如 Cloud Foundry)来保持它的活力。在独立模式下,你可能更喜欢关闭客户端行为,这样它就不会继续尝试而无法与其对等方联系。下面的示例展示了如何关闭客户端行为:
application.yml(独立的 Eureka 服务器)
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
请注意,serviceUrl
指向与本地实例相同的主机。
# 2.5.同伴意识
通过运行多个实例并要求它们彼此注册,可以使 Eureka 更具弹性和可用性。实际上,这是默认的行为,因此要使其工作,你所需要做的就是向对等方添加一个有效的serviceUrl
,如下面的示例所示:
application.yml(两个对等的 Eureka 服务器)
---
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
,则不需要eureka.instance.hostname
(默认情况下,通过使用java.net.InetAddress
来查找它)。
你可以向系统中添加多个对等节点,并且,只要它们都通过至少一个边缘彼此连接,它们就会在它们之间同步注册。如果对等点在物理上是分开的(在一个数据中心内部或多个数据中心之间),那么原则上,系统可以经受住“分裂大脑”式的故障。你可以向系统中添加多个对等节点,并且只要它们都直接相互连接,它们就会在它们之间同步注册。
应用程序.yml(三个对等的 Eureka 服务器)
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
# 2.6.何时选择 IP 地址
在某些情况下,对 Eureka 来说,更好的方法是宣传服务的 IP 地址,而不是主机名。将eureka.instance.preferIpAddress
设置为true
,并且,当应用程序向 Eureka 注册时,它使用其 IP 地址而不是其主机名。
如果主机名不能由 Java 确定,则将 IP 地址发送到 Eureka。 设置主机名的唯一方法是通过设置 eureka.instance.hostname 属性。你可以在运行时使用环境变量设置你的主机名——例如, eureka.instance.hostname=${HOST_NAME} 。 |
---|
# 2.7.保护 Eureka 服务器
你只需通过spring-boot-starter-security
将 Spring 安全性添加到你的服务器的 Classpath,就可以保护你的 Eureka 服务器。默认情况下,当 Spring 安全性在 Classpath 上时,它将要求在向应用程序发送每个请求时都发送一个有效的 CSRF 令牌。Eureka 客户机通常不会拥有有效的跨站点请求伪造令牌,你将需要为/eureka/**
端点禁用此要求。例如:
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
有关 CSRF 的更多信息,请参见Spring Security documentation (opens new window)。
可以在 Spring Cloud样本repo (opens new window)中找到演示 Eureka 服务器。
# 2.8.JDK11 支援
在 JDK11 中删除了 Eureka 服务器所依赖的 JAXB 模块。如果打算在运行 Eureka 服务器时使用 JDK11,则必须在 POM 或 Gradle 文件中包含这些依赖关系。
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
# 3.配置属性
要查看所有 Spring Cloud Netflix 相关配置属性的列表,请检查附录页。