稳固知新-分布式-服务注册与发现
- Posted by 微博@Yangsc_o
- 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
摘要
本文通过zk和eureka来探究分布式系统 CAP的原则、探究分布式系统中的服务发现不同方案的选择,两者有着不同的设计思路,探究不同,对比差异,方便学习,本文要求对zk和eureka有基础的了解,zk的特性和功能在之前的博客里已有描述,在此文不在赘述;
CAP原则
CAP原则指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。CAP原则是NOSQL数据库的基石。
分布式系统的CAP理论:理论首先把分布式系统中的三个特性进行了如下归纳:
- 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
- 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
- 分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
CAP三个特性只能满足其中两个,那么取舍的策略就共有三种:
-
CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。传统的关系型数据库RDBMS:Oracle、MySQL就是CA。
-
CP without A:如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。
-
AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。
根据上面的分析,也就是说在分布式存储系统中,最多只能实现上面的两点。而由于网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须需要实现的,所以也就剩下CP、AP的方案;概念已经分析完毕,今天主要以zk和eureka为例探究分布式系统中的服务发现不同方案的选择。
分布式系统-服务发现
在分布式系统中,我们需要将自己提供的服务暴露给使用方,dubbo、springcloud是目前在国内相对比较流畅两套为服务架构,dubbo使用zk作为服务发现、springcloud使用eureka作为服务发现,但二者又较大区别;
Zookeeper遵守CP
zookeeper是一种提供强一致性的服务。网络上大多数人描述zookeeper的缺点:当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求高于一致性。但是zk会出现这一种情况,当master节点因为网络故障与其他节点失去联系时,剩余注册功能就会重新进行leader选举看。问题在于,选举leader的时间太长,30~120s,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的;
我个人觉得zk的优势在于,zk采用订阅的方式,如果服务节点有变化,zkclient能很快感知到,实时性更高一些,针对zookeeper的缺点,dubbo也做了针对性优化,比如zk真的挂掉了,不影响服务直接的相互调用,在我看来针对性的优化还是很有优势的。
Eureka遵守AP
Eureka组件
包含两个组件:Eureka Server和Eureka Client。
- Eureka Server 提供服务注册服务,各个节点启动后会在 Eureka Server 中进行注册,这样 EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
- Eureka Client 是一个java客户端,用于简化与 Eureka Server 的交互,客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
Feature:
- Eureka 不持久化缓存,重启后内存数据丢失;
- Eureka 通过增量更新注册信息,只关心瞬时状态;
- Eureka 提供客户端缓存,宁可返回某服务5分钟之前可用的服务器列表信息,也不能因为暂时的网络故障而找不到可用的服务器,满足 CAP 中的 AP。
服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
当服务注册中心Eureka Server检测到服务提供者因为宕机、网络原因不可用时,则在服务注册中心将服务置为DOWN状态,并把当前服务提供者状态向订阅者发布,订阅过的服务消费者更新本地缓存。
服务提供者在启动后,周期性(默认30秒)向Eureka Server发送心跳,以证明当前服务是可用状态。Eureka Server在一定的时间(默认90秒)未收到客户端的心跳,则认为服务宕机,注销该实例。
Eureka缓存
Eureka Server 存在三个变量(三级缓存机制):
- registry(ConcurrentHashMap):实时更新,类 AbstractInstanceRegistry 成员变量,UI 端请求的是这里的服务注册信息。
- readWriteCacheMap(Guava Cache/LoadingCache):实时更新,类 ResponseCacheImpl 成员变量,缓存时间 180 秒。
- readOnlyCacheMap(ConcurrentHashMap):周期更新,类 ResponseCacheImpl 成员变量,默认每30s从 readWriteCacheMap 更新,Eureka client 默认从这里更新服务注册信息,可配置直接从 readWriteCacheMap 更新
Eureka Server 通过上面的三个变量来保存服务注册信息。默认情况下定时任务每 30s 将 readWriteCacheMap 同步至 readOnlyCacheMap,每 60s 清理超过 90s 未续约的节点,Eureka Client 每 30s 从 readOnlyCacheMap 更新服务注册信息,而 UI 则从 registry 更新服务注册信息;
Eureka Server 通过上面的三个变量来保存服务注册信息。默认情况下定时任务每 30s 将 readWriteCacheMap 同步至 readOnlyCacheMap,每 60s 清理超过 90s 未续约的节点,Eureka Client 每 30s 从 readOnlyCacheMap 更新服务注册信息,而 UI 则从 registry 更新服务注册信息。
了解了上面的缓存原理,Eureka的缺点也就很明显了,实时性不够,但省去了zookeeper的中途选举leader时服务不可用的问题,对比起来似乎有一些优势;
可以参考一下网络上的配置
Server Configuration
## 中小规模下,自我保护模式坑比好处多,所以关闭它
eureka.server.enableSelfPreservation=false
## 心跳阈值计算周期,如果开启自我保护模式,可以改一下这个配置
## eureka.server.renewalThresholdUpdateIntervalMs=120000
## 主动失效检测间隔,配置成5秒
eureka.server.evictionIntervalTimerInMs=5000
## 心跳间隔,5秒
eureka.instance.leaseRenewalIntervalInSeconds=5
## 没有心跳的淘汰时间,10秒
eureka.instance.leaseExpirationDurationInSeconds=10
## 禁用readOnlyCacheMap
eureka.server.useReadOnlyResponseCache=false
Client Configuration
## 心跳间隔,5秒
eureka.instance.leaseRenewalIntervalInSeconds=5
## 没有心跳的淘汰时间,10秒
eureka.instance.leaseExpirationDurationInSeconds=10
# 定时刷新本地缓存时间
eureka.client.registryFetchIntervalSeconds=5
# ribbon缓存时间
ribbon.ServerListRefreshInterval=2000
经过调优后,eureka也能到达秒级;
其它
当然市面上还有其它的其它的方案,比如阿里巴巴nacos等,新的技术出现、流行一定是为了弥补现在出现的问题,当然公司在选择切换的时候也有一定成本,如果作为公司的架构师、技术专家,那么一定也会考虑更多、权衡利弊之后才会做出抉择;nacos并不在本文讨论的范围之内,可以根据参考链接的《Nacos与Eureka区别及如何选型》做对比。