cooldatabase 2019-07-01
在2015-2016,我们将单体应用程序重新设计为微服务,并选择Spring Cloud Netflix作为基础。
Spring Cloud Netflix 是通过自动配置, Spring 环境以加上其他 Spring 编程模型习惯用法的一个对Netflix OSS 开源软件进行集成的 Spring Boot 应用程序。
时间流逝,我们逐渐对框架更加熟悉,甚至还贡献了一些代码。我认为我已经知道了够多了, 便开始研究 Eureka 冗余和故障转移。当一个概念的快速证明不起作用时,我开始深入挖掘,遇到了其他可怜的灵魂, 在互联网上搜索类似的信息。不幸的是, 网上啥也找不到。值得赞扬的是,Spring Cloud 文档相当不错,而且还有 Netflix Wiki,但都没有达到我想要的详细程度。这篇文章试图弥合这一差距。我假设您对 Eureka 和服务发现有一些基本的了解,所以如果您是新手,请在阅读 Spring Cloud 文档后再回来。
Netflix 的高级架构,遵循 Apache License v2.0 许可。
Eureka 有两个基本组件,服务器和客户端。引用 Netflix Wiki:
Eureka 是一种基于 REST(Representational State Transfer)的服务,主要用于 AWS 云,用于定位服务,以实现中间层服务器的负载均衡和故障转移。我们将此服务称为 Eureka Server。Eureka 还附带了一个基于Java 的客户端组件 Eureka Client,它使与服务的交互变得更加容易。客户端还有一个内置的负载均衡器,可以进行基本的循环负载均衡。
Eureka 客户端应用程序称为实例。客户端应用程序和 Eureka 客户端之间存在细微差别; 前者是您的应用程序,后者是框架提供的组件。
Netflix 设计的 Eureka 具有高度动态性。有一般属性,也有一些专门属性, 用于定义了在一般属性的更新查询时间间隔。这是一种常见的做法,这意味着大多数这些属性可以在运行时更改,并在下一个刷新周期中被感知。例如,客户端用于向 Eureka 注册的 URL 可以更改,并在5分钟后被感知(可通过eureka.client.eurekaServiceUrlPollIntervalSeconds来配置)。大多数用户不需要这样的动态更新,但如果你想这样做,可以找到所有配置选项,如下所示:
org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean 实现com.netflix.eureka.EurekaServerConfig。所有属性都具有eureka.server 前缀。
org.springframework.cloud.netflix.eureka.EurekaClientConfigBean 实现 com.netflix.discovery.EurekaClientConfig。所有属性都有 eureka.client 前缀。
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean 实现(间接)com.netflix.appinfo.EurekaInstanceConfig。所有属性都有 eureka.instance 前缀。
有关更多体系结构的详细信息,请参阅 Netflix Wiki。
在 Eureka 中通过 eureka.instance.instanceId 识别实例, 如果不存在则用eureka.instance.metadataMap.instanceId。实例发现彼此使用 eureka.instance.appName,该值在 Spring Cloud 默认使用 spring.application.name 或者 UNKNOWN, 如果前者未定义。您需要进行设置,spring.application.name 因为具有相同名称的应用程序在 Eureka 服务器中聚集在一起。不需要设置eureka.instance.instanceId,因为它默认值设置为 CLIENT IP:PORT,但如果你想设置它,则appName必须在集群范围内是唯一的。还有一个 eureka.instance.virtualHostName,但它没有被 Spring 使用,并且设置为 spring.application.name 或 UNKNOWN 如上所述。
如果 registerWithEureka 是 true,则实例使用给定的 URL 向 Eureka 服务器注册; 然后,它每30秒发送一次心跳(可配置 eureka.instance.leaseRenewalIntervalInSeconds)。如果服务器没有收到心跳,则在eureka.instance.leaseExpirationDurationInSeconds 从注册表中删除实例之前等待90秒(可配置),然后禁止该实例的流量。发送心跳是一项异步任务; 如果操作失败,则以指数方式后退2倍,直到eureka.instance.leaseRenewalIntervalInSeconds * eureka.client.heartbeatExecutorExponentialBackOffBound 达到最大延迟。注册 Eureka 的重试次数没有限制。
心跳与将实例信息更新到 Eureka 服务器不同。每个实例都由 com.netflix.appinfo.InstanceInfo 表示,它是关于实例的一堆信息。它将 InstanceInfo 会定期发送到Eureka 服务器,从启动后40s开始(可配置eureka.client.initialInstanceInfoReplicationIntervalSeconds),然后每30秒开始一次(可配置eureka.client.instanceInfoReplicationIntervalSeconds)。
如果 eureka.client.fetchRegistry 是 true,则客户端在启动时获取 Eureka 服务器注册表并在本地进行缓存。从那时起,它只是获取增量(这可以通过设置来关闭 eureka.client.shouldDisableDelta 到 false,尽管这会是浪费带宽)。注册表获取是每30秒调度一次的异步任务(可配置eureka.client.registryFetchIntervalSeconds)。如果操作失败,则按指数2倍后退,直到eureka.client.registryFetchIntervalSeconds * eureka.client.cacheRefreshExecutorExponentialBackOffBound 达到最大延迟。获取注册表信息的重试次数没有限制。
客户端任务由 com.netflix.discovery.DiscoveryClient 安排, 这个类是对 Spring Cloud 的org.springframework.cloud.netflix.eureka.CloudEurekaClient 的扩展实现。
大部分都是从spring-cloud-netflix#373复制而来的。
第一次心跳在启动后发生30s(如前所述),因此实例在此间隔之前不会出现在 Eureka 注册表中。
服务器维护一个每30秒更新一次的响应缓存(可配置 eureka.server.responseCacheUpdateIntervalMs)。因此,即使实例刚刚注册,它也不会出现在对 /eureka/apps
REST 端点的调用结果中。但是,实例可能会在注册后出现在 Eureka Dashboard 上。这是因为仪表板绕过了 REST API 使用的响应缓存。如果您知道instanceId,您仍然可以通过调用从 Eureka 获取有关它的一些详细信息/eureka/apps/<appName>/<instanceId>
。此端点不使用响应缓存。
因此,其他客户端可能需要另外30秒来发现新注册的实例。
Eurekac客户端维护注册表信息的缓存。此缓存每30秒刷新一次(如前所述)。因此,在客户端决定刷新其本地缓存并发现其他新注册的实例之前,可能还需要30秒。
Ribbon 使用的负载均衡器从本地 Eureka 客户端获取其信息。Ribbon 还维护一份本地缓存,以避免为每个请求调用客户端。此缓存每30秒刷新一次(可配置 ribbon.ServerListRefreshInterval)。因此,在 Ribbon 可以使用新注册的实例之前可能还需要30秒。
最后,在新注册的实例开始接收来自其他实例的流量之前,可能需要2分钟。
Eureka 服务器具有对等感知模式,在该模式下,它跨其他 Eureka 服务器复制服务注册表,以提供负载平衡和弹性。对等感知模式是默认模式,因此 Eureka 服务器也充当 Eureka 客户端向对等体上注册给定的 URL。这就是你应该如何在生产中运行 Eureka,但是对于演示或概念验证,你可以通过设置 registerWithEureka 为 false 采用独立模式运行。
当 Eureka 服务器启动时,它会尝试从对等的 Eureka 节点获取所有注册表信息。对每个对等体重试此操作5次(可配置 eureka.server.numberRegistrySyncRetries)。如果由于某种原因此操作失败,则服务器不允许客户端获取注册表信息5分钟(可配置 eureka.server.getWaitTimeInMsWhenSyncEmpty)。
Eureka 的对等感知意识,通过所谓的“自我保护”的概念引入一个全新水平的复杂性(可以通过设置eureka.server.enableSelfPreservation 为 false 来关闭)。事实上,在网上看,这是我看到大多数人绊倒的地方。来自 Netflix Wiki:
当 Eureka 服务器启动时,它会尝试从相邻节点获取所有实例注册表信息。如果从某个节点获取信息时出现问题,服务器会尝试所有对等体直到放弃。如果服务器能够成功获取所有实例,则会根据该信息设置应接收的续订阈值。如果有任何时间,续订低于为该值配置的百分比,则服务器会停止使用实例过期机制, 以保护当前实例注册表信息。
数学运算如下:如果有两个客户端注册到 Eureka 实例,每个客户端每30秒发送一次心跳,该实例应该在一分钟内收到4次心跳。Spring 为此添加了一个较低的最小值1(可配置eureka.instance.registry.expectedNumberOfRenewsPerMin),因此实例希望每分钟接收5个心跳。然后将其乘以0.85(可配置eureka.server.renewalPercentThreshold)并四舍五入到下一个整数,这使我们再次回到 5。如果 Eureka 在15分钟内收到的心跳少于5次(可配置 eureka.server.renewalThresholdUpdateIntervalMs),它将进入自我保护模式并不再让已经注册的实例过期。
Eureka 服务器隐含假设客户端以每30秒固定的速率发送心跳。如果注册了两个实例,则服务器希望(2 2 + 1 ) 0.85 = 5每分钟都接收一次心跳。如果续订率低于此值,则激活自保护模式。现在,如果客户端发送心跳的速度要快得多(例如,每隔10秒),则服务器每分钟接收12次心跳,并且即使其中一个实例发生故障,也会持续接收6次/分钟。因此,即使应该是自保护模式也不会被激活。这就是改变 eureka.client.instanceInfoReplicationIntervalSeconds 不可取的原因。如果必须的话, 你可以修改一下 eureka.server.renewalPercentThreshold 的值。
Eureka 对等实例不考虑预期的续约数量,但他们的心跳被计入在最后一分钟收到的续约数量。在对等感知模式下,心跳可以转到任何 Eureka 实例; 这在运行负载均衡器或 Kubernetes 服务后很重要,其中心跳以循环模式(通常)发送到每个实例。
在更新超过阈值之前,Eureka服务器不会退出自我保护模式。这可能导致客户端获取不再存在的实例。请参阅了解Eureka Peer to Peer Communication.
还有一件事:在同一主机上运行多个 Eureka 服务器。Netflix 代码(com.netflix.eureka.cluster.PeerEurekaNodes.isThisMyUrl)过滤掉同一主机上的对等 URL。这可能是为了防止服务器注册为自己的对等体(我在这里猜测),但由于它们不检查端口,因此除非 Eureka 主机名eureka.client.serviceUrl.defaultZone 不同,否则对等感知不起作用。这种情况的Hack解决方法是定义唯一的主机名,然后在/etc/hosts
文件(或它的Windows等价物)将它们映射到 127.0.0.1 了。Spring Cloud doc讨论了这种解决方法,但没有提到为什么需要它。现在你知道了。
AWS Regions 和可用 Zones。
Eureka 旨在在 AWS 中运行,并使用许多特定于 AWS 的概念和术语。Regions 和 Zones 是两个这样的东西。来自 AWS doc:
Amazon EC2 托管在全球多个地点。这些位置由 Regions 和可用 Zones 组成。每个地区都是一个独立的地理区域。每个区域都有多个孤立的位置,称为可用Zones...每个区域都是完全独立的。每个可用 Zones 都是隔离的,但区域中的可用 Zones 通过低延迟链接连接。
Eureka 仪表板显示环境和数据中心。的值被分别设置为 test 与 default,通过使用com.netflix.config.ConfigurationManager 设置 org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap。有各种查找和回退,因此如果由于某种原因需要更改它们,请参阅上述类的源代码。
Eureka 客户端默认情况下更喜欢相同的区域(可配置eureka.client.preferSameZone)。来自com.netflix.discovery.endpoint.EndpointUtils.getServiceUrlsFromDNS Java doc:
从DNS获取所有 Eureka 服务 URL 的列表,以便 Eureka 客户端与之交谈。客户端从其区域中获取服务 URL,然后随机故障转移到其他区域。如果同一区域中有多个服务器,则客户端会再次随机选择一个服务器。这样,流量将在发生故障时分发。
Ticket spring-cloud-netflix#203 在撰写本文时是开放的,其中有几个人谈论 Regions和 Zones。我没有验证,所以我无法评论Regions和 Zones如何与 Eureka一起使用。
大部分都是从 spring-cloud-netflix#203 中复制而来的。
HA 策略似乎是一个主要的 Eureka 服务器(server1)与备份(server2)。
通过配置(或 DNS 或 /etc/hosts
)向客户端提供 Eureka 服务器列表
客户端尝试连接server1; 在这一点上,server2坐着闲着。
如果server1不可用,客户端将从列表中尝试下一个。
当server1重新上线时,客户端会重新使用server1。
当然server1,server2可以在对等感知模式下运行,并且可以复制其注册表。但这与客户注册正交。
作者:阿比吉特 萨卡(Abhijit Sarkar) 软件工程师,摄影师,通常是个好人。
Updated: January 18, 2017