Eureka原理分析
Eureka 的目标
原来:负载均衡器会根据配好的 IP 和主机名来进行负载均衡,但是对 AWS cloud 这样体量的系统来说,因为服务实例宕机恢复十分频繁,所以负载均衡器还会有一个更复杂的注册 / 注销服务的机制。
现在:Eureka 在中间层提供一种负载均衡的可能。
服务发现比较
Feature | Consul | zookeeper | etcd | euerka |
---|---|---|---|---|
服务健康检查 | 服务状态,内存,硬盘等 | (弱)长连接,keepalive | 连接心跳 | 可配支持 |
多数据中心 | 支持 | — | — | — |
kv存储服务 | 支持 | 支持 | 支持 | — |
一致性 | raft | paxos(zab) | raft | — |
cap | ca | cp | cp | ap |
使用接口(多语言能力) | 支持http和dns | 客户端 | http/grpc | http(sidecar) |
watch支持 | 全量/支持long polling | 支持 | 支持 long polling | 支持 long polling/大部分增量 |
自身监控 | metrics | — | metrics | metrics |
安全 | acl /https | acl | https支持(弱) | — |
spring cloud集成 | 已支持 | 已支持 | 已支持 | 已支持 |
纵向(功能)比较
服务的健康检查
Euraka 使用时需要显式配置健康检查支持;Zookeeper,Etcd 则在失去了和服务进程的连接情况下任务不健康,而 Consul 相对更为详细点,比如内存是否已使用了90%,文件系统的空间是不是快不足了。
多数据中心支持
Consul 通过 WAN 的 Gossip 协议,完成跨数据中心的同步;而且其他的产品则需要额外的开发工作来实现。
KV 存储服务
除了 Eureka ,其他几款都能够对外支持 k-v 的存储服务,所以后面会讲到这几款产品追求高一致性的重要原因。而提供存储服务,也能够较好的转化为动态配置服务哦。
产品设计中 CAP 理论的取舍(这一段感觉是瞎说的,博客下面评论产生很多争议)
Eureka 典型的 AP,作为分布式场景下的服务发现的产品较为合适,服务发现场景的可用性优先级较高,一致性并不是特别致命。其次 CA 类型的场景 Consul,也能提供较高的可用性,并能 k-v store 服务保证一致性。 而Zookeeper,Etcd则是CP类型 牺牲可用性,在服务发现场景并没太大优势。
多语言能力与对外提供服务的接入协议
Zookeeper的跨语言支持较弱,其他几款支持 http11 提供接入的可能。Euraka 一般通过 sidecar的方式提供多语言客户端的接入支持。Etcd 还提供了Grpc的支持。 Consul除了标准的Rest服务api,还提供了DNS的支持。
Watch的支持(客户端观察到服务提供者变化)
Zookeeper 支持服务器端推送变化,Eureka 2.0(正在开发中)也计划支持。 Eureka 1,Consul,Etcd则都通过长轮询的方式来实现变化的感知。
自身集群的监控
除了 Zookeeper ,其他几款都默认支持 metrics,运维者可以搜集并报警这些度量信息达到监控目的。
安全
Consul,Zookeeper 支持ACL,另外 Consul,Etcd 支持安全通道https。
Spring Cloud的集成
目前都有相对应的 boot starter,提供了集成能力。
总的来看,目前Consul 自身功能,和 spring cloud 对其集成的支持都相对较为完善,而且运维的复杂度较为简单(没有详细列出讨论),Eureka 设计上比较符合场景,但还需持续的完善。
Eureka VS ZooKeeper
- Eureka 能提供 REST 接口来动态调整配置、renewals、expiration、cancel 等;
- Eureka 倾向于高可用,而不是 ZooKeeper 的高一致性。
- Eureka 可以集成到应用中,ZooKeeper 只能作为一个外部组件提供服务,这会增加复杂性、增加系统崩溃的几率。
组成部分
- 负载均衡:Eureka Client 提供最简单的轮询负载均衡策略,可以封装 Eureka 并根据更多的因素(流量、资源使用、异常发生频次等)来提供一种更好的弹性伸缩特性。
- 分区:每个 Region 有一个 Eureka 集群用于处理该区域服务失败的情况,各 Region 之间是不会互相通信的。
- 服务注册到 Eureka Server 后每 30 秒发送一次心跳(heartbeats)来刷新租约(lease),如果网络出现分区或者 Eureka 宕机了,这种心跳自然会停止,如果达到了Renews threshold(即 Server 期望在每分钟中收到的心跳次数,需要考虑是否禁用服务器的自注册、Server/Client 数量等,暂时取默认值 85%就好),Eureka Server 就会将其从服务注册表中移除。
- 服务注册信息会自动同步到整个 Eureka Server 集群,这也意味着它们是对等的 P2P 集群。
- 集成到业务服务中的 Eureka Client 可以查询服务注册信息(默认每 30 秒一次)来定位服务及进行远程调用。
服务状态机
- STARTING:启动中的状态,应用可以在这个阶段做一些初始化工作
- UP:可以正常进行通信;
- DOWN:心跳停了,一般是宕机了或者网络出现了分区
- OUT_OF_SERVICE:因为某些特殊原因无法提供服务,比如 Elasticsearch 因为没有达到最小可用分片数,或者由于蓝绿发布的需要,新版本如果发布后有问题可以直接将实例状态置为 OUT_OF_SERVICE 来达到回滚的目的。
- UNKNOWN:WTF?
Client 与 Server 间的交互
Register
Eureka Client 将信息注册到 Eureka Server,注册过程发生在第一次心跳时(在 30 秒后)。
Unregister
正常情况下,Client 必须显式调用 Unregister 来释放自己的注册信息,除非是由于”unclean termination”而导致心跳丢失超过 3 次。
Renew
客户端每30秒通过发送一次心跳(heartbeats)来续约(renewal),心跳告知Eureka Server本实例仍然存活,如果Server在90秒内没有收到续约请求,它将从服务注册表中移除该实例。
Fetch Registry
Eureka clients fetches the registry information from the server and csort_bufferhes it locally. After that, the clients use that information to find other services. This information is updated periodically (every 30 seconds) by getting the delta updates between the last fetch cycle and the current one. The delta information is held longer (for about 3 mins) in the server, hence the delta fetches may return the same instances again. The Eureka client automatically handles the duplicate information.
After getting the deltas, Eureka client reconciles the information with the server by comparing the instance counts returned by the server and if the information does not match for some reason, the whole registry information is fetched again. Eureka server caches the compressed payload of the deltas, whole registry and also per application as well as the uncompressed information of the same. The payload also supports both JSON/XML formats. Eureka client gets the information in compressed JSON format using jersey apache client.
Cancel
Eureka client sends a cancel request to Eureka server on shutdown. This removes the instance from the server’s instance registry thereby effectively taking the instance out of traffic.
This is done when the Eureka client shuts down and the application should make sure to call the following during its shutdown.
DiscoveryManager.getInstance().shutdownComponent()
Time Lag
All operations from Eureka client may take some time to reflect in the Eureka servers and subsequently in other Eureka clients. This is because of the caching of the payload on the eureka server which is refreshed periodically to reflect new information. Eureka clients also fetch deltas periodically. Hence, it may take up to 2 mins for changes to propagate to all Eureka clients.
Communication mechanism
Eureka Client默认使用Jersey发送基于Jackson封装的JSON数据包给Eureka Server。
通信协议
Eureka 不限制通信协议,Thrift、HTTP(S)等均可。
高可用
Eureka Client 的高可用设计:
- Client 中有服务注册表的缓存,即使所有 Server 都挂掉了,Client 还是能继续工作。
- 刚开始,Eureka Client 会尝试与同一 zone(可视为同一局域网)中的 Eureka Server 交互,如果交互出现问题或同一 zone 中没有可用的 Eureka Server,则它将转向其他 zone。
Eureka Server 的高可用设计:
- 启动 Server 时从邻居节点获取注册信息,一个不行换另一个,直到获取成功,如果从邻居节点均无法获取到注册信息,则它会等待几分钟(默认 5 分钟)让 Client 注册它们的信息
Server 之间获取服务注册信息的机制和 Client 从 Server 获取的一样。
获取成功后,Server 会设置Renewal Threshold
并开始接收 Client 的心跳; - 保护模式:如果
Renews(last min)
(上一分钟内收到的心跳次数)达到了Renews threshold
(Server 期望在每分钟中收到的心跳次数,一般是 3),或者过去 15 分钟内的统计数据小于eureka.server.renewalPercentThreshold
(renews / renews threshold 的比值,默认为 0.85,当在 15 分钟内微服务心跳数低于 85%,则 Server 会进入自我保护状态,在这种情况下 Server 不会删除注册信息),则进入保护模式,自我保护状态其实是为了防止突发网络不稳定或断电时微服务心跳数剧减,导致微服务注册信息被大量删除的情况。
在保护模式下,Client 可能从 Server 得到已经不可用的 IP(服务器已不存在或因某些原因无法响应),因此 Client 必须保证这种情况下的弹性高可用,比如快速地超时并重试其他服务器。 - 退出保护模式:在保护模式下,Eureka Server 会停止移除服务注册信息,直到满足如下条件中的任意之一:
- 心跳
Renews
达到了Renews threshold
; - 保护模式被禁用,设置
eureka.server.enableSelfPreservation=false
。
- 心跳
- 孤儿 Server:当发生网络分区,一些 Eureka Server 可能会成为
orphaned server
,一些 Client 会注册到这些 Server 上,导致一些 Client 能看到这些注册信息而其他的一些则不能。
当网络恢复后,Server 的 P2P 集群能正常地交互,注册信息会被自动同步到所有 Server 上。
异常情况
比如在测试环境中出现:
1 | EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE. |
解决办法:
- 在生产上可以开自注册,部署两个 server
- 在本机器上测试的时候,可以把比值调低,比如 0.49
- 或者简单粗暴把自我保护模式关闭:eureka.server.enableSelfPreservation=false
Eureka 配置
配置:
Configuring Eureka
Eureka Server 开放的 REST 接口提供动态配置功能:
Eureka REST operations
添加自定义元数据
静态设置:
1 | eureka.metadata.mykey=myvalue |
设置后,相当于将mykey:myvalue
添加到 eureka 的metadata map
中。
动态设置:
需要提供一个自定义的
获取:
1 | String myValue = instanceInfo.getMetadata().get("myKey"); |
源码
原生客户端的执行过程
EurekaClient
通过 DI(依赖注入)使用 EurekaClient
ExampleEurekaGovernatedService
配置
DefaultEurekaClientConfig extends EurekaClientConfig
EurekaServerConfig extends DefaultEurekaServerConfig
CloudInstanceConfig extends PropertiesInstanceConfig
MyDataCenterInstanceConfig extends PropertiesInstanceConfig
To dynamically do this, you will need to first provide your own custom implementation of the EurekaInstanceConfig
interface. You can then overload the public Map<String, String> getMetadataMap() method to return a metadata map that contains the desired metadata values. See PropertiesInstanceConfig
for an example implementation that provides the configuration based system above.