Spring-Cloud 服务注册与发现 Eureka
CAP
在分布式系统领域有个著名的CAP定理:C -- 数据一致性(Consistency),A -- 服务可用性(Availability),P -- 分区容错性(Partition tolerance)。
数据一致性
一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。服务可用性
可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。分区容错性
分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。一个分布式系统最多只能同时CAP这三项中的两项。
服务注册与发现
对于微服务的治理而言,核心就是服务的注册和发现。这方面的开源架构很多,最出名的应该是Zookeeper,但这也许并不代表就是一个最佳选择。
Zookeeper是著名Hadoop的一个子项目。Zookeeper保证的是CP,即选择保证数据一致性和分区容错性,代价是它无法保证每次服务请求的可用性。从实际情况来分析,在使用Zookeeper获取服务列表时,如果zookeeper正在选主,或者Zookeeper集群中半数以上机器不可用,那么将就无法获得数据了。所以说,Zookeeper不能保证服务可用性。
对于设计数据存储场景的分布式环境,的确应当把数据一致性作为首要考虑的方向,这也是zookeeper设计成CP的原因。
但是对于服务发现场景来说,数据一致性就不再是首要考虑的。对某个服务来说,即使注册中心的每个节点所保存的服务提供者信息有所不同,也并不会造成灾难性的后果。因为对于服务消费者来说,关注的是可以消费,哪怕说拿到了不正确的服务实例去消费,也比无法消费要好一些。所以,对于服务发现而言,可用性理应比数据一致性更加值得首要考虑,即AP胜过CP。所以Spring Cloud Netflix在设计Eureka时遵守的就是AP原则。
Eureka
从上图可以看出,Eureka的组成实例分为如下两种:Eureka Server和Eureka Client。
- Eureka Server,服务的注册中心,负责维护注册的服务列表。
- Service Provider,服务提供方,Eureka Client,向Eureka Server做服务注册、续约和下线等操作,注册的主要数据包括服务名、机器ip、端口号、域名等等。
- Service Consumer,服务消费方,Eureka Client,向Eureka Server获取Service Provider的注册信息,并通过远程调用与Service Provider进行通信。
图中的Service Provider(服务提供者)和Service Consumer(服务发现者)只是使用上的角色区别而已,并不是严格的概念,在实际使用中,二者角色可以互换,设置可以同时兼有两种角色的功能。
Spring Cloud针对服务注册与发现,提供了三种实现:Eureka、Consul、Zookeeper。目前支持得最好的就是Eureka,其次是Consul,最后是Zookeeper。
Eureka Server
Eureka Server作为一个独立的部署单元,以REST API的形式为服务实例提供了注册、管理和查询等操作。同时,Eureka Server也为我们提供了可视化的监控页面,可以直观地看到各个Eureka Server当前的运行状态和所有已注册服务的情况。Eureka Server的高可用集群
Eureka Server通过运行多个实例来构建集群,解决单点问题,与ZooKeeper的leader/flower选举机制不同,Eureka Server采用的是Peer to Peer对等通信。这是一种去中心化的架构,并不存在主从之分,每一个Peer都是对等的。在这种模式下,各个节点之间通过互相注册来提高可用性。每个节点都可被视为其他节点的副本。
如果某台Eureka Server宕机,Eureka Client的请求会自动切换到新的Eureka Server节点,当宕机的服务器重新恢复后,Eureka会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会在节点间进行复制操作,将请求复制到其他Eureka Server当前所知的所有节点中。
一个新的Eureka Server节点启动后,会首先尝试通过从邻近节点获取所有实例注册表信息,来完成自身的完成初始化。
Eureka Server通过getEurekaServiceUrls()方法获取所有的节点,并且会通过心跳续约的方式定期更新。默认配置下,如果Eureka Server在一定时间内没有接收到某个服务实例的心跳,Eureka Server将会注销该实例。当Eureka Server节点在短时间内丢失过多的心跳时(比如发生了网络分区故障),那么这个节点就会进入自我保护模式。下图为Eureka官网的架构图
自我保护模式
默认配置下,如果Eureka Server每分钟收到心跳续约的数量低于一个阈值(instance的数量*(60/每个instance的心跳间隔秒数)*自我保护系数),并且持续15分钟,就会触发自我保护。在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计思想就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。
Service Provider
服务注册
Service Provider本质上是一个Eureka Client。它启动时,会调用服务注册方法,向Eureka Server注册自己的信息。Eureka Server会维护一个已注册服务的列表,这个列表为一个嵌套的hash map:- 第一层,application name和对应的服务实例。
- 第二层,服务实例及其对应的注册信息,包括IP,端口号等。
续约与剔除
前面提到过,服务实例启动后,会周期性地向Eureka Server发送心跳以续约自己的信息,避免自己的注册信息被剔除。续约的方式与服务注册基本一致:首先更新自身状态,再通过节点间复制同步到其它Peer。
如果Eureka Server在一段时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(自我保护模式除外)。
Service Consumer
Service Consumer本质上也是一个Eureka Client。它启动后,会从Eureka Server上获取所有实例的注册信息,包括IP地址、端口等,并缓存到本地(默认每30秒更新一次)。前文提到过,如果与Eureka Server通信中断,Service Consumer仍然可以通过本地缓存与Service Provider通信。实际开发Eureka的过程中,有时会遇见Service Consumer获取到Server Provider的信息有延迟,这是因为服务端的更改可能需要2分钟才能传播到所有客户端(Eureka
Wiki总并没有给出原因),Eureka有三处缓存和一处延迟。
- Eureka Server对注册列表进行缓存,默认时间为30s。
- Eureka Client对获取到的注册信息进行缓存,默认时间为30s。
- Ribbon会从上面提到的Eureka Client获取服务列表,将负载均衡后的结果缓存30s。
- 如果不是在Spring Cloud环境下使用这些组件(Eureka, Ribbon),服务启动后并不会马上向Eureka注册,而是需要等到第一次发送心跳请求时才会注册。心跳请求的发送间隔默认是30s。Spring Cloud对此做了修改,服务启动后会马上注册。