SpringCloud Eureka是SpringCloud Netflix服务套件中的一部分,它基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。
Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是极为重要的,有了服务发现与注册,只需要使用服务标识符就可以访问到服务,而不需要修改服务调用的配置文件。功能类似于Dubbo的注册中心,比如Zookeeper。
Eureka既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用java编写,所以Eureka主要适用于通过java实现的分布式系统,或是JVM兼容语言构建的系统。Eureka的服务端提供了较为完善的REST API,所以Eureka也支持将非java语言实现的服务纳入到Eureka服务治理体系中来,只需要其他语言平台自己实现Eureka的客户端程序。
【1】服务注册、剔除与发现
服务注册
在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,包括服务的主机与端口号、服务版本号、通讯协议等一些附加信息。注册中心按照服务名分类组织服务清单,同时还需要以心跳检测的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。
服务剔除
有些时候,服务实例并不一定会正常下线,可能由于内存溢出、网络故障等原因使服务不能正常运作。而服务注册中心并未收到“服务下线”的请求,为了从服务列表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
服务发现
在服务治理框架下,服务间的调用不再通过指定具体的实例地址来实现,而是通过服务名发起请求调用实现。服务调用方通过服务名从服务注册中心的服务清单中获取服务实例的列表清单,通过指定的负载均衡策略取出一个服务实例位置来进行服务调用。
【2】Eureka服务端与客户端
Eureka服务端
Eureka服务端,即服务注册中心。它同其他服务注册中心一样,支持高可用配置。依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。
Eureka服务端支持集群模式部署,当集群中有分片发生故障的时候,Eureka会自动转入自我保护模式。它允许在分片发生故障的时候继续提供服务的发现和注册,当故障分配恢复时,集群中的其他分片会把他们的状态再次同步回来。集群中的的不同服务注册中心通过异步模式互相复制各自的状态,这也意味着在给定的时间点每个实例关于所有服务的状态可能存在不一致的现象。
各个节点启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
服务同步
从eureka服务治理体系架构图中可以看到,不同的服务提供者可以注册在不同的服务注册中心上,它们的信息被不同的服务注册中心维护。
此时,由于多个服务注册中心互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中相连的其他注册中心,从而实现服务注册中心之间的服务同步。通过服务同步,提供者的服务信息就可以通过集群中的任意一个服务注册中心获得。
Eureka客户端
Eureka客户端,主要处理服务的注册和发现。客户端服务通过注册和参数配置的方式,嵌入在客户端应用程序的代码中。在应用程序启动时,Eureka客户端向服务注册中心注册自身提供的服务,并周期性的发送心跳(默认30S)来更新它的服务租约。同时,他也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期行的刷新服务状态。
EurekaClient 是一个Java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期30秒)。如果Eureka Server 在多个心跳周期内(默认90S)没有接收到某个节点的心跳,EurekaServer(定时任务)将会从服务注册表中把这个服务节点移除。
服务续约
在注册服务之后,服务提供者会维护一个心跳用来持续高速Eureka Server,“我还在持续提供服务”,否则Eureka Server的剔除任务会将该服务实例从服务列表中排除出去。我们称之为服务续约。
下面是服务续约的两个重要属性:
① eureka.instance.lease-expiration-duration-in-seconds
leaseExpirationDurationInSeconds,表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间。默认值90S,在这个时间内若没收到下一次心跳,则将移除该instance。
如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了。
如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉。
该值至少应该大于leaseRenewalIntervalInSeconds
② eureka.instance.lease-renewal-interval-in-seconds
leaseRenewalIntervalInSeconds,表示eureka client发送心跳给server端的频率,默认时间30s。
如果在leaseExpirationDurationInSeconds后,server端没有收到client的心跳,则将摘除该instance。
除此之外,如果该instance实现了HealthCheckCallback,并决定让自己unavailable的话,则该instance也不会接收到流量。
【3】Eureka与Dubbo(Zookeeper)流程示意图
Eureka示意图
Dubbo示意图
【4】Netflix在设计Eureka时遵守的AP原则
即可用性与可靠性,它与Zookeeper这类强调CP(一致性、可靠性)的服务治理框架最大的区别就是,Eureka为了实现更高的服务可用性,牺牲了一定的一致性,在极短情况下它宁愿接收故障实例也不要丢到健康实例。
比如,当服务注册中心的网路发生故障断开时,由于所有的服务实例无法维持续约心跳,在强调AP的服务治理中会把所有服务实例都剔除掉,而Eureka则会因为超过85%的实例丢失心跳而触发保护机制,注册中心将会保留此时的所有节点,以实现服务间依然可以进行互相调用的场景。即使其中有部分故障节点,但这样做可以继续保障大多数的服务正常消费。
CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
【5】Eureka的自我保护机制
有时会出现以下现象:
或者如下现象:
总之就是某时刻一个服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存—自我保护模式。
何为自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区发生故障时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了–因为微服务本身是健康的,此时不应该注销该服务。
服务注册到Eureka Server后,会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间会统计实例心跳失败的比例在15分钟以之内是否低于85%,如果出现低于的情况,Eureka Server会将当前实例注册信息保护起来,让这些实例不会过期。
Eureka通过”自我保护”模式来解决这个问题—-当EurekaServer在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式后,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会再注销服务)。当网络故障恢复后,该EurekaServer节点会自动退出自我保护模式。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳书重新恢复到阈值以上时,该EurekaServer节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲:好死不如赖活着。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康和不健康的都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
另一方面,自我保护机制会使客户端很容易拿到实际已经不存在的服务实例,会出现调用失败的情况。因此客户端要有容错机制,比如请求重试、断路器。
在Spring Cloud中,可以如下关闭自我保护模式(建议开启):
eureka.server.enable-self-preservation = false
【6】Eureka的高可用性(集群)
考虑到发生故障的情况,服务注册中心发生故障必将会造成整个系统的瘫痪,因此需要保证服务注册中心的高可用。
Eureka Server在设计的时候就考虑了高可用设计,在Eureka服务治理设计中,所有节点既是服务的提供方,也是服务的消费方,服务注册中心也不例外。
Eureka Server的高可用实际上就是将自己做为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。
Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。可以采用两两注册的方式实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现。
示意图如下:
【7】服务消费
① 获取服务
消费者服务启动时,会发送一个Rest请求给服务注册中心,来获取上面注册的服务清单。为了性能考虑,Eureka Server会维护一份只读的服务注册清单来返回给客户端,同时该缓存清单默认会每隔30秒更新一次。
下面是获取服务的两个重要的属性:
eureka.client.fetch-registry
是否需要去检索寻找服务,默认是true。
eureka.client.registry-fetch-interval-seconds
表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒。
② 服务调用
服务消费者在获取服务清单后,通过服务名可以获取具体提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在Ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。
③ 服务下线
在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭操作时,会触发一个服务下线的Rest服务请求给Eureka Server,告诉服务注册中心:“我要下线了。”服务端在接收到该请求后,将该服务状态置位下线(DOWN),并把该下线事件传播出去。
【8】Eureka与Zookeeper区别
著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性P是在分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。
这里回忆一下传统关系型数据库和非关系型数据库的特性。
传统关系型数据库:MySQL、Oracle、sqlserver
特性:A(Atomicity)原子性、C(Consistency)一致性、I(Isolation)独立性和D(Durability)持久性。
非关系型数据库:HBase、Redis、MongoDB
特性:C(Consistency)强一致性、A(Availability)高可用性和P(Partition tolerance)分区容错性。
CA/CP/AP
CA:单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP:满足一致性、分区容错性的系统,通常性能不是特别高。
AP:满足可用性、分区容错性的系统,通常可能对一致性要求低一些。
而Eureka和Zookeeper核心的区别是:Eureka保证的是AP,Zookeeper保证的是CP。
Zookeeper保证CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接收服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。
但是Zookeeper会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的世界太长,30-120s,且选举期间整个Zookeeper集群是不可用的。这就导致在选举期间注册服务瘫痪。
在云部署的环境下,因网络问题使得Zookeeper集群失去master节点是较大概率会发送的事,虽然服务器最终能够恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka保证AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka客户端在向某个Eureka注册时如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过不保证查询到的信息是最新的(不保证强一致性)。
除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka不再从注册列表中移除因为长时间没有收到心跳而应该过期的服务;
- Eureka仍然能够接受新服务的注册和查询请求,但是不会同步到其他节点上(即保证当前节点依然可用);
- 当网络稳定时,当前实例新的注册信息会被同步到其他节点中。
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像Zookeeper那样导致整个注册服务瘫痪。