一、Zookeeper简介
Zookeeper是一个开源的分布式应用程序协调服务器。其为分布式系统提供一致性服务。
其一致性服务主要是通过Paxos算法和ZAB协议完成的。
其主要功能包括:配置维护、域名服务、分布式同步、集群管理等。
二、一致性
Zookeeper的具有一致性,主要是因为ZK有以下几个特点:
顺序一致性:zk接收到多个事务请求,其会按照严格的接收顺序进行处理。
原子性:所有事务请求的结果在zk集群中的每一台主机上应用的情况都是一致的,要么全成功,要么全失败。
单一视图:无论客户端连接的是集群中的哪一台服务器,其看到的数据都是一致的。
可靠性:一旦zk成功的完成了一个事务,那么该事务操作的结果都会被保留下来,直到有其他事务成功修改该数据。
最终一致性:一旦一个事务被应用成功,zk集群的各个节点都会在最短的时间内完成数据同步。
三、Paxos算法
(一)算法描述
paxos算法主要解决的是分布式系统中如何就一个决议达成一致的问题。
分布式系统中各个节点采用两种通讯模式:共享内存和消息传递;而paxos算法是基于消息传递通讯模型的。
同时,对于消息传递,有可能出现拜占庭将军问题(即通讯过程中可能发生各种情况,进而导致发送内容与接收内容不一致),因此,paxos算法的前提是不存在拜占庭将军问题,也就是说信道是安全可靠的,集群中各节点传递的消息是不会被篡改的。
在paxos算法中,存在三种角色:Proposer(提案者)、Acceptor(接收者、决议者)、Leaner(学习者、同步者)
paxos算法的一致性主要体现在以下几点:
1、每个提案者在提出提案时都会生成一个递增的、全局唯一的编号N,然后将该编号赋值给提案。(关于N的生成方式,有两种:全局性生成器和提案者自身维护)
2、每个表决者在接收到提案后,会将该提案编号N记录在本地,这样每个表决者本地就会存储提案的最大编号MaxN,决议者只会接收编号N比MaxN大的提案。
3、在众多提案中只能选择一个提案被选定
4、一旦一个提案被选定,则其他学习者会主动同步该提案到本地。
5、没有提案被提出,则不会有提案被选定。
paxos算法对于提案的提交分为两个阶段,准备阶段prepare和接收阶段accept
prepare阶段:
1、提案者准备提交一个编号为N的提案,于是先向所有表决者发送一个prepare(N)请求,用于试探表决者是否支持该编号的决议。
2、每个表决者中都保存着自己曾经的接收到的最大提案编号MaxN,当服务器接收到其他主机发来的prepare请求时,会将请求中的编号N与本地中存储的MaxN进行对比
(1)N小于MaxN,则不接受该提案,当前表决者采取不回应或回应error的方式来拒绝该prepare请求。
(2)N大于MaxN,当前表决者会将提案N记录下来,并将其原来最大的接受提案返回Proposal(myid,maxid,value),其中myid指的是原提案的发起者,maxid表示该服务器保存的最大提案编号,value表示提案的具体内容;如果该服务器之前没有接收过提案,则返回的是Proposal(null,null,null)
(3)在prepare阶段,N肯定不会等于MaxN,这是由N的生成机制决定的,要获得N的值,其必须要在原有数值的基础上采用同步锁的方式增一。
accept阶段:
1、当提案者(Proposer)发出prepare(N)后,若收到了超过半数表决者(Accept),那么提案者就会将其真正的提案Proposal(myid,N,value)发送给所有的表决者
2、当表决者接收到提案者发送的提案Proposal(myid,N,value)提案后,会再次对比N与本地已接收的最大提案编号MaxN或Prepare阶段的最大编号,如果N大于这两个编号,则会将编号为N的提案保存到本地,否则表决者则采取不回应或回应error的方式来拒绝该提案。
3、若提案者没有收到超过半数的表决者accept反馈,则有可能产生两种结果:放弃提交提案;重新发起prepare请求,N增一。
4、当提案者接收到的反馈超过半数,其会向外部发送两种广播:
(1)向曾接收其提案的表决者发送“可执行数据同步信号”,即commit信号,让表决者执行其接收到的提案
(2)向未曾接收其提案的表决者发送“提案 + 可执行数据同步信号”,即Proposal + commit,让其执行接收提案并执行提案。
(二)Paxos活锁问题
Paxos算法在实际应用中会有许多不便的地方,因此也就出现了很多对paxos算法的优化算法,例如:MultiPaxos、Fast Paxos、EPaxos等
例如Paxos算法存在活锁问题,Fast Paxos算法对Paxos算法做了改进,只允许一个进程提交提案,即该进程具有对N的唯一操作权,因此就解决了活锁问题。
那么什么是活锁呢:举个栗子,例如有A、B两个服务器,A先提交提案,编号为1,即prepare(1),其他服务器会将N=1记录下来,B服务器此时也提交提案,编号此时自增为2,即prepare(2);此时A服务器将真正的提案发给所有决议者proposal(A,1,VALUE),决议者中此时存储的prepare阶段最大的提案编号为2,那么提案1则会失败,因此,A服务器会重新提交提案,编号自增为3;此时那么决议者中记录的prepare阶段最大的提案编号为3,那么当提案2将实际提案内容发送给决议者时,仍然会出现提案1出现的问题,一直依此类推,最终都没有提案决议通过。
四、ZAB协议
(一)ZAB协议与Paxos的关系
ZAB,即Zookeeper Atomic Broadcast,zk原子广播协议。是专门为Zookeeper设计的一种支持崩溃恢复的原子广播协议。在Zookeeper中,主要依赖ZAB实现分布式数据一致性。
Zookeeper使用单一主线程来接收并处理客户端的事务请求。当服务器的数据状态发生变更后,集群采用ZAB原子广播协议,以事务提案Proposal的形式广播到所有的副本进程上,ZAB能保证一个全局的变更序列,即为每一个事务分配一个唯一的事务id(xid)。
当客户端连接到任意一个Zookeeper服务器时,若客户端发起读请求,则服务器直接返回自己保存的数据,如果客户端发起写请求,如果该服务器不是leader服务器,则会将请求转给leader服务器。leader服务器会以提案的方式广播该写操作,如果超过半数的节点同意该写操作,该写操作就会被提交,然后leader会再以广播的形式广播给所有的订阅者,即leaner,通知他们同步数据。
通过上述说明,其实可以发现,ZAB协议就是Paxos算法的工业实现,但是两者的设计目标不太一样,ZAB协议主要用于构建一个高可用的分布式住从系统,即follower是leader的从机,如果leader挂了,马上就可以选举出一个新的leader,但平时他们都对外部提供服务。而Fast Paxos算法则是用于构建一个分布式一致性状态机系统,确保系统中各个节点的状态都是一致的。
(二)ZAB协议中的角色、数据、模式、状态
三类角色:
为了避免Zookeeper出现单点问题,zk也是以集群的形式出现的,zk集群中的角色主要有以下三类:
Leader:事务请求的唯一处理者,也可以处理读请求
Follower:可以直接处理客户端读请求,但不会处理事务请求,如果有事务请求,会将事务请求转给Leader处理;Leader选举过程的参与者,句有选举权和被选举权。
ObServer:可以理解为没有选举权和被选举权的Follower
这三种角色在不同的情况下又有一些不同的名称:
Leaner:学习者,即需要从Leader中同步数据的Server,也就是Follower + ObServer
QuorumServer:法定服务器,也就是具有选举权和被选举权的服务器 Leader + Follower
这里有个问题,Observer是否是越多越好:
ObServer数量一般与Follower数量相同,并不是Observer越多越好,因为Observer的数量增多虽然不会增加事务操作的压力,但是其需要从Leader同步数据,Observer同步数据的时间小于等于Follower同步数据的时间,所以当Follower同步完成后,Leader的Observer列表中的Observer主机将结束同步。那些完成同步的Observer将会进入到另外一个对外提供服务的Observer列表。那么那些不能提供服务的Observer主机就造成了资源浪费。
所以,对于事务操作频繁的系统,不建议使用过多的Observer。
说明:
Leader中存在两个关于Observer的列表,all(包含所有Observer)和service(包含与Leader通不过数据的Observer),service列表是动态变化的,对于没有进入到service中的Observer,其会通过心跳与Leader进行连接,一旦连接成功,就会马上从Leader同步数据,同步完成后,向Leader发送ACK,Leader在接收到ACK后会将其添加到service列表中;
若客户端连接上了不再service列表中的Observer,那么该Observer是不能对外提供服务的,因为Observer的状态不是Observing,这个状态是通过Observer与Leader间的心跳来维护的。
Leader中对于Follower也有两个集合,all与service,其功能与Observer相似,但不同点是,若Leader收到的Follower同步完成ACK数量没有过半,则认为同步失败,会重新进行广播,让Follower重新进行同步。
三个数据:
在ZAB中有三个重要的数据,zxid、epoch、xid
zxid:是提交提案的服务器编号,其是一个64位长度的long类型,其中高32位表示epoch,低32位表示xid
epoch:(时期、年号)每个Leader选举结束后都会生成一个新的epoch,并会通知到集群中的所有其他Server,包含Follower和Observer
xid:事务id,是一个流水号
三种模式:
ZAB中对zkServer的状态有三种模式,这三种模式并没有明显的界限,他们相互交织在一起
恢复模式:在集群启动过程中或Leader崩溃后,系统都需要进入恢复模式,以恢复系统对外提供服务的能力,其包含两个阶段:Leader选举和初始化同步
广播模式:分为两类,初始化广播和更新广播
同步模式:分为两类,初始化同步和更新同步
四种状态:
zk集群的每一台主机,在不同阶段会处于不同的状态,每一台主机具有四种状态:
LOOKING:选举状态
FOLLOWING:Follower的正常工作状态
OBSERVING:Observer的正常工作状态
LEADING:Leader的正常工作状态
(三)同步模式与广播模式
1、初始化广播
上面提到,恢复模式具有两个阶段:Leader选举与初始化同步(广播)。
当完成Leader选举后,此时的Leader还是一个准Leader,其要经过初始化同步后才能成为真正的Leader
具体过程如下:
(1)为了保证Leader向Leaner发送提案的有序,Leader会为每一个Leaner服务器创建一个队列。
(2)Leader将那些没有被各个Leaner同步的事务封装成一个Proposal
(3)Leader将这些Proposal逐条发送给Leaner,并在每一个Proposal后都跟一个commit消息,表示该事务已提交,Leaner可以直接接收并执行。
(4)Leaner接收来自Leader的Proposal,并将其更新到本地
(5)当Leaner更新成功后,会向准Leader发送ACK消息
(6)Leader服务器在收到来自Leaner的ACK后就会将该Leaner加入到真正可用的Follower列表或ObServer列表。如果没有反馈ACK,或者反馈了ACK但是Leader没有接收到信息,Leader不会将其加入到相应列表。
2、消息广播算法
当集群中的Leaner完成了初始化状态同步,那么整个zk集群就进入了正常的工作模式,如果Leaner节点收到客户端的事务请求,那么这些Leaner就会将请求转给Leader服务器,然后再执行如下过程:
(1)Leader接收到事务请求后,为事务赋予一个全局唯一的64位自增id,即zxid,通过zxid的大小比较即可实现事务的有序性管理,然后将事务封装成一个Proposal。
(2)Leader根据Follower列表获取到所有的Follower,然后再将Proposal通过这些Follower的队列将提案发送给各个Follower。
(3)当Follower接收到提案后,会将提案的zxid与本地记录的事务日志中的最大zxid相比,若当前提案的zxid大于最大的zxid,则将当前提案记录到本地事务日志中,并向Leader返回一个ACK。
(4)当Leader收到过半的ACK后,Leader就会向所有的Follower队列发送Commit消息,向所有的Observer发送Proposal
(5)当Follower收到commit消息后,就会将本地日志中的事务正式更新到本地;当Observer收到Proposal后,会直接将事务更新到本地。
(6)无论是Follower还是ObServer,在同步完成后都需要向Leader发送成功ACK。
(四)恢复模式的三个原则
当集群正在启动过程中,或Leader崩溃后,集群就进入了恢复模式,对于要恢复的数据状态要遵循三个原则:
1、Leader主动让出原则
若集群中Leader收到Follower心跳数量没有过半,此时Leader会自认为自己与集群的连接出现了问题,其会主动修改自己的状态为LOOKING,去查找新的Leader。
这时就会造成其他server认为已经丢失了Leader(已过半),所以他们会发起新的Leader选举,选举出一个新的Leader。
2、已被处理过的消息不能丢原则
当Leader收到超过半数的Follower返回的ACK后,就向所有的Follower广播COMMIT消息,当各个server收到commit后,就会在本地执行写操作,然后向客户端响应写成功。
但是入股在非全部Follower收到commit消息之前Leader就挂了,这会导致有一部分Server执行了事务,有一部分Server没有执行事务;当新的Leader被选举出来后,集群经过恢复模式需要保证所有的Server上都执行了这些只被部分Server执行过的事务。
3、被丢弃的消息不能重现原则
当在Leader新事物已经通过,其已经将新事物更新到了本地,但是所有的Follower还没有收到COMMIT之前,Leader宕机了,那么重新选举后的Leader及所有Follower中是没有该事务的,如果原来的Leader恢复后,作为Follower重新加入到集群,此时该Follower就会比其他Server多了内容,就会造成集群内数据不一致,因此这种数据是需要被丢弃的。
(五)Leader选举
在集群启动过程中或者Leader宕机后,集群就进入了恢复模式,恢复模式中最重要的就是Leader选举。
首先,在Leader选举中有几个概念:
myid:也成为serverId,这是zk集群中服务器的唯一标识。
逻辑时钟:Logicalclock,是一个整型数,其在选举时称为logicalclock,而在选举结束时称为epoch。
集群启动时的Leader选举流程如下:
(1)由于是集群启动阶段,因此每个Server中都没有事务id,所以当Server1启动时,其会投票给自己,并发布自己的投票结果(1,0),这里说明一下,同步投票结果的第一个参数是本服务器的myid,第二个参数是事务id(zxid),此时由于其他服务器还没有启动,因此还收不到其他服务的反馈信息,Server1一直处于Looking状态。
(2)当第二台服务器启动时,同样投票给自己,然后同步给其他服务器自己的选票结果(2,0),此时server1和server2可以进行通讯,每个服务器可以接收到其他服务器的投票结果,并且需要判断该投票的有效性,检查是否是本轮投票,是否是来自Looking状态的服务器。
(3)针对每一个投票,服务器都会与自己的投票进行PK,首先检查ZXID,ZXID大的作为Leader,如果ZXID一致,那么就比较myid,myid大的作为Leader。因此在上述场景中,Server1对比后发现Server2的投票比自己的投票更适合,那么Server1就会重新发送一个(2,0)的选票,而Server2在第一个收到Server1的投票结果(1,0)时,发现没有自己的合适,就不处理
(4)而第二次收到Server1的投票结果(2,0)时,发现该投票结果已经过半,Server1接收到的(2,0)投票结果也已过半,那么此时就选举出了新的Leader,server2。
(5)确定了新的Leader后,每个服务器都会更新自己的状态,如果是Follower则更新状态为Following,如果是Observer则更新状态为Obsering,如果是Leader则更新为Leading。
(6)如果Leader已经选举成功,此时Server3启动,其想进行一轮新的选举,但是由于目前集群处于正常工作状态,所以其只能以Follow的身份加入到集群中。
宕机后的Leader选举:
还按照上面的场景,如果Server2宕机,此时便开始新的一轮Leader选举:
(1)变更状态,由于Leader宕机,那么Follower就会将状态由Following变更为LOOKING,然后进入Leader选举。
(2)每个Server都需要投票,仍然会先投自己,但是由于在运行期间,各个服务器的事务id可能会不一样,例如Server1的zxid为111,Server3的zxid为333,那么Server1的投票信息为(1,1111),Server3的投票信息为(3,333),那么根据上面说的选举规则,Server1会重新发送一个(3,333)的投票
(3)然后做投票统计,最终内容为(3,333)的投票超过了半数,那么此时就选举出了新的Leader,即为Server3
(4)更改服务器的状态
五、高可用集群容灾
上面提到过,无论是Leader选举还是写操作,都需要半数以上的节点通过才可以,也就是说如果出现超过半数的主机宕机,则投票永远不会通过,基于该理论,由5台服务器构成的集群最多只能宕机2台服务器,而由6台服务器组成的集群最多也是只能宕机2台服务器,也就是说5台服务器的集群和6台服务器的集群的容灾能力是相同的,因此,基于容灾能力的考虑,建议集群中主机的数量设置为奇数,以避免资源浪费。
但是从系统吞吐量上来说,6台服务器肯定要比5台服务器的性能高,因此从吞吐量上来说,6台主机并不是资源浪费。
对于一个高可用的系统来说,除了部署多台服务器为一个集群来避免单点问题外,需要要考虑将集群部署在多个机房,多余多个机房中服务器的部署,要充分考虑过半原则。也就是说,要尽量确保zk集群中有过半的服务器可以正常运行。
在生产环境中,三机房的部署是最常见且容灾性最好的部署方案。
三机房部署中要求每个机房中的主机数量要少于集群总数的一半,也就是说有一个机房出现断电断网等问题,不会影响集群的使用。
对于双机房部署,只能是让一个机房的服务器数量超过集群总数的一半,使其作为主机房,那么,当主机房出现问题时,就会造成集群不可用。
六、CAP定理
CAP是指在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。
一致性:分布式系统中多个主机之间是否能够保持数据的一致性,当数据发生变更后,各个主机仍然可以保持一致
可用性:系统提供的服务必须一直都可以对外提供服务
分区容错性:分布式系统在遇到其中部分主机发生故障时,仍然可以保证一致性或者可用性
对于分布式系统,网络环境是不可控的,出现网络分区是不可避免的,因此必须要支持分区容错性,但是其并不能同时保证一致性和可用性,因此对于一个分布式系统来说,只能满足两项,即要么是AP,要么是CP
BASE理论是Basically Available(基本可用)、Soft state(软状态) 和 Eventually consistent(最终一致性) 三个短语的简写,是CAP定理基于一致性和可用性权衡的结果。
基本可用:指的是分布式系统在出现不可预知故障的时候,允许损失部分可用性,例如损失响应时间或者是功能上的损失
软状态:指的是允许数据存在中间状态,并认为该中间状态不会影响系统的整体可用性,即允许系统服务器间数据同步过程存在一定的延时
最终一致性:指的是系统中所有的中间状态,在经过一段时间同步后,最终能够达到一个一致的状态,因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证数据的强一致性。
对于ZK来说,其遵循的是CP,即保证了一致性而牺牲了可用性,因为当Leader宕机后,zk集群会马上进行新的Leader选举,选举时长一般在200毫秒内,最长不会超过60秒,在选举过程中,整个zk集群是不对外提供服务的,因此其保证了一致性,而放弃了可用性。
七、ZK脑裂
脑裂问题主要出现在多机房部署的情况下,例如双机房部署,如果两个机房此时网络出现了问题,那么就会进入Leader选举,由于机房网络不通,导致每个机房都是选举出一个Leader,从而造成脑裂。
但是由于zk有过半原则保护,即需要超过半数的节点同意才能够被选举为Leader,因此zk最多只会有一个Leader,所以,zk不存在脑裂问题。