Zookeeper是一个开源的分布式应用程序协调服务器,其为分布式系统提供一致性服务。其一致性是通过基于Paxos算法的ZAB协议完成的。其主要功能包括:配置维护、域名服务、分布式同步、集群管理等。
1.1.1功能简介
(1)配置维护
分布式系统中,很多服务都是部署在集群中的,即很多台服务器中部署着完全相同的应用,起着完全相同的作用。当然,集群中的这些服务器的配置文件是完全相同的。
若集群中服务器的配置文件需要进行修改,那么我们就需要逐台修改这些服务器中的配置文件。如果我们集群服务器比较少,那么这些修改还不是太麻烦,但如果集群服务器特别多,比如某些大型互联网公司的Hadoop集群有数千台服务器,那么纯手工的更改这些配置文件几乎就是一件不可能完成的任务。即使使用大量人力进行修改,但是过多人员参与,出错的概率大大提升,对于集群的形成的危险是很大的。
这时候Zookeeper就可以派上用场了。其对于配置文件的维护采用的是发布/订阅模型。发布者将修改好的集群的配置文件发布到Zookeeper服务器的文件系统中,那么订阅者马上就可以接收到通知,并主动的去同步Zookeeper里的配置文件。Zookeeper具有同步操作的原子性,确保每个集群服务器的配置文件都能被正确的更新。
(2)域名服务
在分布式应用中,一个项目包含多个工程,有些工程是专门为其他工程提供服务的。一个项目中可能会存在多种提供不同服务的功能,而一种服务又可能存在多个提供者(服务器)。所以,用于消费这些服务的客户端工程若要消费这些服务器,就变得异常的复杂了。
此时,Zookeeper就可以上场了。为每个服务起一个名称,将这些服务的名称与提供这些服务的主机地址注册到Zookeeper中,形成一个服务映射表。服务消费者只需要通过服务名称即可享受到服务,而无需了解服务具体的提供者是谁。服务的减少、添加、变更,只需修改Zookeeper中的服务映射表即可。
阿里的Dubbo就是使用Zookeeper作为服务域名服务器。
(3)分布式同步
在分布式系统中,很多运算(对请求的处理)过程是由分布式集群中的若干服务共同计算完成的,并且他们之间的运算还具有逻辑上的先后顺序。如何保证这些服务器运行期间的同步性呢?
使用Zookeeper可以协调这些服务器间运算的过程。让这些服务器都同时监听Zookeeper上的的同一个znode(Zookeeper文件系统中的一个数据存储节点),一旦其中一个服务器update了znode,那么另一个相应服务器能够收到通知,并作出相应处理。
(4)集群管理
集群管理中最麻烦的就是节点故障管理。Zookeeper可以让集群选出一个健康的节点作为Master,Master随时监控着当前集群中的每个节点的健康状况,一旦某个节点发生故障,Master会把这个情况立即通知给集群中的其他节点,使其他节点对于任务的分配作出相应的调整。Zookeeper不仅可以发现故障,也会对故障进行甄别,如果该故障可以修复,Zookeeper可以自动修复,若不能修复则会告诉系统管理员错误的原因让管理员迅速定位问题。
但这里也有个问题:Master故障了,那怎么办?Zookeeper内部有一个选举算法,当Master故障出现时,Zookeeper能马上选出新的Master对集群进行管理。
1.1.2 一致性要求
什么是zk的一致性呢?其需要满足以下几点要求:
(1)顺序一致性
同一个客户端发起的n多个事务请求(写请求),最终将会严格按照其发起顺序被应用到Zookeeper中。
(2)原子性
所有事务请求的结果在集群中所有机器上的应用情况是一致的。也就是说要么整个集群所有主机都成功应用了某一个事务,要么都没有应用,不会出现集群中部分主机应用了该事务,而另外一部分没有应用到的情况。
(3)单一视图
无论客户端连接的是哪个Zookeeper服务器,其看到的服务端数据模型都是一致的。
(4)可靠性
一旦服务端成功的应用了一个事务(写操作),并完成了对客户端的响应,那么该事务所引起的服务端状态变更将会一直被保留下来,除非有另一个事务又对其进行了变更。
(5)实时性
通常人们看到实时性的第一反应是,一旦一个事务被成功应用,那么客户端能够立即从服务端上读取到这个事务变更后的最新数据状态。这里需要注意的是,Zookeeper仅仅保证在一定的时间段内,客户端最终一定能够从服务器上读取到最新的数据状态。
1.1.1 Session
Session是指客户端会话。Zookeeper对外的服务端口默认是2181,客户端启动时,首先会与zk服务器建立一个TCP长连接,从第一次连接建立开始,客户端会话的生命周期也开始了。通过这个长连接,客户端能够通过心跳检测保持与服务器的有效会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能通过该连接接收来自服务器的Watcher事件通知。
Session的SessionTimeout值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开链接等各种原因导致客户端连接断开时,只要在SessionTimeout规定的时间内客户端能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。
1.2.2 znode
Zookeeper的文件系统采用树形层次化的目录结构,与unix文件系统非常相似。每个目录在zookeeper中叫做一个znode,每个znode拥有一个唯一的路径标识,即名称。Znode可以包含数据和子node(临时节点不能有子node)。Znode中的数据可以有多个版本,所以查询某路径下的数据需带上版本号。客户端应用可以在znode上设置监视器(watcher)。
1.2.3 watcher机制
Zk通过watcher机制实现了发布/订阅模式。Zk提供了分布式数据的发布/订阅功能,一个发布者能够让多个订阅者同时监听某一主题对象,当这个主题对象状态发生变化时,会通知所有订阅者,使他们能够做出相应的处理。Zk引入了watcher机制来实现这种分布式的通知功能。Zk允许客户端(订阅者)向服务端(发布者)注册一个watcher监听,当服务端的一些指定事件触发这个watcher,那么就会向指定客户端发送一个事件通知。而这个事件通知则是通过tcp长连接的session完成的。
1.2.4 ACL
ACL全称为Access Control List(访问控制列表),用于控制资源的访问权限,是zk数据安全的保障。Zk利用ACL策略控制znode节点的访问权限,如节点数据读写、节点创建、节点删除、读取子节点列表、设置节点权限等。
在传统的文件系统中,ACL分为两个维度:组和权限。一个组可以包含多种权限,一个文件或目录拥有了某个组的权限即拥有了组里的所有权限。文件或子目录默认会继承其父目录的ACL。
而在zookeeper中,znode的ACL是没有继承关系的,每个znode的权限都是独立控制的,只有客户端满足znode设置的权限要求时,才能完成相应的操作。Zookeeper的ACL分为三个维度:授权策略scheme、用户id、用户权限permission。
1.3.1 算法简介
Paxos算法是莱斯利·兰伯特1990年提出的一种基于消息传递的、具有高容错性的一致性算法。Google chubby(分布式锁服务)的作者说过,世上只有一种一致性算法,那就是Paxos
,所有其他一致性算法都是Paxos的不完整版。Paxos算法是一种公认的晦涩难懂的算法,并且工程实现上也具有很大的难度。较有名的Paxos工程实现有google chubby,ZAB,微信的PhxPaxos等。
Paxos算法是用于解决什么问题的呢?Paxos算法要解决的问题是,在分布式系统中如何就某个决议达成一致。
1.3.2 Paxos与拜占庭将军问题
拜占庭将军问题,是由Paxos算法作者提出的点对点通信中的基本问题。该问题要说明的含义是,在存在消息丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的。
Paxos算法的前提是不存在拜占庭将军问题,即信道是安全的、可靠的,集群节点间传递消息是不会被篡改的。在实际工程实践中,大多数系统都是部署在一个局域网内,因此消息被篡改的情况很少;另一方面,由于硬件和网络原因而造成的消息不完整问题,现在已经不再是问题,只需要一套简单的校验算法即可。因此,在实际工程中各个服务器间消息传递过程可以认为不存在拜占庭将军问题。
一般情况下,分布式系统中各个节点间采用两种通讯模型:共享内容(shared memory)、消息传递(messages passing)。而Paxos是基于消息传递通讯模型的。
1.3.3 算法描述
(1)三种角色
在Paxos算法中有三种角色,分别具有三种不同的行为。但很多时候,一个进程可能同时充当多个角色。
一个提案的表决者(Acceptor)会存在多个,但在一个集群中,提议者(Proposer)也是可能存在多个的,不同的提议者(Proposer)会提出不同的提案。而一致性算法则可以保证如下几点:
(2)算法过程描述
Paxos算法的执行过程划分为两个阶段:准备阶段prepare与接受阶段accept。
prepare阶段
accept阶段
(3)算法过程举例
假设有三台主机,他们要从中选出一个Leader。这三台主机在不同的时间分别充当提案的提议者Proposer、表决者Acceptor及学习者Learnor三种不同的角色。
这里首先介绍一下该举例的前提:每个提议者(Proposer)都想提议自己要当Leader,假设三个提议者Proposer-1、Proposer-2、Proposer-3提议的Proposer-1初始编号依次为20、10、30.每个提议者都要将提案发送给所有的表决者(Acceptor),为了便于理解,假设都只有两个(超过半数)表决者收到消息:Accepter-2与Acceptor-3收到Proposer-2的消息;Accepter-1与Acceptor-2收到了Proposer-1的消息;Accepter-2与Acceptor-3收到了Proposer-3的消息。
A、prepare阶段
假设Proposer-1发送的prepare(20)消息先达到Acceptor-1和Acceptor-2。因为他们之前都没有接受过prepare请求,所以他们都是直接接收该请求,并将Proposal(server1-id,null,null)与Proposal(server2-id,null,null)反馈给提议者Proposer-1,同时Acceptor-1和Acceptor-2记录下目前收到的最大提议编号maxN为20,即其以后不会再接受编号小于20的请求。Proposer-1收到了超过半数的反馈。
紧接着Proposesr-2的prepare(10)消息到达Acceptor-2和Acceptor-3.由于Acceptor-3还未曾接受过其他请求,所以其直接接受Proposer-2的prepare(10)的请求,并返回Proposer-2 Proposal(server3-id,null,null),Acceptor-3会记录下其目前收到的最大提议编号maxN为10,即其以后不会再接受编号小于10的请求。另外对于Acceptor-2来说,由于其已经接受Proposer-1的提案20,则会回应error或者不回应的方式告诉Proposer-2不接受其提案,此时Proposeser-2接收到的反馈数量是1,没有超过半数,则会递增自己的编号,这里我们默认递增10,然后会继续重复发送prepare()
B、accept阶段
a、Proposer的提交
b、Acceptor的表决
由于Acceptor-2和Acceptor-3已经通过了提案Proposal(server2-id,40,server2),并达成了超过半数的一致性,server2马上成为了Leader,选举状态结束。Server2会发布广播给所有Leaner,通知它们来同步数据。同步完成后,集群进入正常服务状态。
1.3.4 Paxos算法优化
前面所述的Paxos算法在实际工程应用过程中,根据不同的实际需求存在诸多不便之处,所以也就出现了很多对于基本Paxos算法的优化算法,例如,Multi Paxos、Fast Paxos、EPaxos。而Zookeeper的Leader选举算法FastLeaderElection则是Fast Paxos算法的工程应用。
1.4.1 ZAB协议简介
ZAB,zookeeper Atomic Broadcast,zk原子消息广播协议,是专为zookeeper设计的一种支持崩溃回复的原子广播协议,是一种Paxos协议的优化算法,在Zookeeper中,主要依赖ZAB协议来实现分布式数据一致性。
Zookeeper使用一个但一进程来接受并处理客户端的所有事务请求,即写请求。当服务器数据的状态发生变更后,集群采用ZAB原子广播协议,以事务提案Proposal的形式广播到所有的副本进程上。ZAB协议能够保证一个全局的变更序列,即可以为每一个事务分配一个全局的递增编号xid。
当Zookeeper客户端连接到Zookeeper集群的一个节点后,若客户端提交的是读请求,那么当前节点就直接根据自己保存的数据对其进行相应;如果是写请求且当前节点不是Leader,那么节点就会将该写请求转发给Leader,Leader会以提案的方式广播该写操作,只要有超过半数节点统一该写操作,则该写操作请求就会被提交。然后Leader会再次广播给所有订阅者,即Leaner,通知它们同步数据。
1.4.2 三类角色
为了避免Zookeeper的单点问题,zk也是以集群的形式出现的。Zk集群中的角色主要有一下三类:
1.4.3 三种模式.
ZAB协议中对zkServer的状态描述有三种模式:恢复模式、同步模式和广播模式。
1.4.4 zxid
Zxid为64位长度的Long类型,其中高32位标识纪元epoch,低32位标识事务标识xid,即zxid由两部分构成:epoch与xid。
每个Leader都会具有不同的epoch值,表示一个时期、时代。每一次新的选举开启后都会生成一个新的epoch,新的Leader产生,则会更新所有zkServer的zxid中的epoch。
Xid则为zk的事务id,每一个写操作都是一个事务,都会有一个xid。xid为一个依次递增的流水号。每一个写操作都需要由Leader发起一个提案,由所有Follower表决是否同意本次写操作,而每个提案都具有一个zxid。
1.4.5 消息广播算法
当集群中已经有过半的Folower与Leader服务器完成了状态同步,那么整个zk集群就可以进入消息广播模式了。
如果集群中的其他节点收到客户端的事务请求,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。Leader服务器会为其生成对应的事务提案Proposal,并将其发送给集群中其余其余所有主机,然后再分别收集他们的选票,在选票过半后进行事务提交。其具体过程如下:
1.4.6 恢复模式的两个原则
当集群正在启动过程中,或Leader与超过半数的主机断连后,集群就进入了恢复模式。对于要恢复的数据状态需要遵循两个原则。
(1)已被处理过的消息不能丢
当Leader收到超过半数Follower的ACKs后,就向各个Follower广播COMMIT消息,批准各个Server执行该写操作事务。当各个Server在接收到Leader的COMMIT消息后会在本地执行该写操作,然后会向客户端响应写操作成功。但是如果在非全部Follower收到COMMIT消息之前Leader就挂了,这将导致一种后果:部分Server已经执行了该事务,而部分Server尚未收到COMMIT消息,所以其并没有执行该事务。当新的Leader被选举出,集群经过恢复模式后需要保证所有Server上都执行了那些已经被部分Server执行过的事务。
为了保证已被处理过的消息不能丢的目的,ZAB的恢复模式使用了以下策略:
(2)被丢弃的消息不能再现
当Leader接收到事务请求并生成了proposal,但还未向Follower发送时就挂了。由于其他Follower并没有收到此proposal,即并不知道该Proposal的存在,因此在经过恢复模式重新选举产生了新的Leader后,这个事务被跳过。在整个集群尚未进入正常服务状态时,之前挂了的Leader主机重新启动并注册成为了Follower。但由于保留了被跳过的proposal,所以其与整个系统的状态是不一致的,需要将该proposal删除。
ZAB通过设计巧妙的zxid实现了这一目的。一个zxid是64位,高32位是纪元epoch编号,每一次选举epoch的值都会增一。低32位是事务标识xid,每产生一个事务,该xid值都会增一。这样设计的好处是旧的Leader挂了后重启,它不会被选举为新的Leader,因此此时他的zxid肯定小于当前新的epoch。当旧的Leader作为Follower接入新的Leader后,新的Leader会让其将所有旧的epoch编号号的、未被COMMIT的proposal清除。
1.4.7 Leader选举算法
当集群正在启动过程中,或Leader与超过半数的主机断连后,集群就进入了恢复模式。而恢复模式中最重要的阶段就是Leader选举。
在集群启动过程中的Leader选举过程(算法)与Leader断连后的Leader选举过程稍微有一些区别,基本相同。
(1)集群启动中的Leader选举
若进行Leader选举,则至少需要两台主机,这里以三台主机组成的集群为例。
在集群初始化阶段,当第一台服务器Server1启动时,会给自己投票,然后发布自己的投票结果。投票包含所推举的服务器的mid和ZXID,使用(mid,ZXID)来表示,此时Server1的投票为(1,0)。由于其他机器还没有启动所以他们收不到反馈信息,Server1的状态一直属于Looking,即属于非服务状态。
当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,选举过程如下:
对于Server1而言,它的投票是(1,0),接受Server2的投票为(2.,0)。其首先会比较两者的ZXID,均为0,在比较myid,此时Server2的myid最大,于是Server1更新自己的投票为(2,0),然后重新投票。对于Server2而言,其无须更新自己的投票,只是再次向集群中所有主机发出上一次投票信息即可。
(2)断连后的Leader选举
在Zookeeper运行期间,Leader与非Leader服务器各司其职,即便当有非Leader服务器宕机或新加入时也不会影响Leader。但是若Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮的Leader选举,其过程和启动时期的Leader选举过程基本一致。
假设正在运行的有Server1,Server2,Server3三台服务器,当前Leader是Server2,若某一时刻Server2挂了,此时便开始新一轮的Leader选举了。选举过程如下:
1.4.8 恢复模式下的数据同步
当完成Leader选举后,就要进入到恢复模式下的数据同步阶段。Leader服务器会为每一个Follower服务器准备一个队列,并将那些没有被各个Follower服务器同步的事务以Proposal的形式逐条发给各个Follower服务器,并在每一个Proposal后紧跟一个commit消息,表示该事务已经被提交,Follower可以直接接收并执行。当Follower服务器将所有尚未同步的事务proposal都从leader服务器同步过来并成功执行后,会向准leader发送ACK信息。Leader服务器在收到该ACK后就会将该follower加入到真正可用的follower列表。
1.5.1 简介
CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。
1.5.2 三二原则
对于分布式系统,在CAP原则中分区容错性P是必须要保证的。但其并不能同时保证一致性与可用性。因为现在的分布式系统在满足一致性的前提下,会牺牲可用性;在满足了可用性的前提下,会牺牲一致性。所以,CAP原则对于一个分布式系统来说,只可能满足两项,即要么CP,要么AP。这就是CAP的三二原则。
1.5.3 ZK与CP
Zk遵循的是CP原则,即保证了一致性,但牺牲了可用性。体现在哪里呢?
当Leader宕机后,zk集群会马上进行新的Leader的选举。但选举时长在30-120秒之间,整个选举期间选举期间kzk集群是不接受客户端的读写操作的,即zi集群是出于瘫痪状态的。所以,其不满足可用性。
为什么Leader的选举需要这么长时间呢?为了保证zk集群各个节点中数据的一致性,zk集群做了两类数据同步:初始化同步与更新同步。当新的Leader被选举出后,各个Follower需要将新的Leader的数据同步到自己的缓存中,这是初始化同步;当Leader的数据被客户端修改后,其会向Follower发出广播,然后各个Follower会主动同步Leader的更新数据,这是更新同步。无论是初始化同步还是更新同步,zk集群为了保证数据的一致性,若发现超过半数的Follower同步超时,则其会再次进行同步,而这个过程中zk集群是处于不可用状态的。
由于zk采用了CP原则,所以导致其可用性降低,这是致命的问题。Spring Cloud的Eureka在分布式系统中所起的作用类似于zk,但其采用了AP原则,其牺牲了一致性,但保证了可用性。