在深入了解zookeeper之前,很多同学可能会认为zookeeper就是paxos算法的⼀个实现,但事实上,zookeeper并没有完全采用paxos算法
,而是使用了⼀种称为Zookeeper Atomic Broadcast
(ZAB,Zookeeper原子消息广播协议)的协议作为其数据⼀致性
的核心算法。
ZAB协议并不像Paxos算法那样是⼀种通用的分布式⼀致性算法,它是⼀种特别为zookeeper专门设计的⼀种支持崩溃恢复的原子广播协议。
在zookeeper中,主要就是依赖ZAB协议来实现分布式数据的⼀致性,基于该协议,Zookeeper实现了⼀种·主备模式·的系统架构来保持集群中各副本之间的数据的⼀致性,表现形式就是使用⼀个单⼀的主进程来接收并处理客户端的所有事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事务Proposal的形式广播到所有的副本进程中,ZAB协议的主备模型架构保证了同⼀时刻集群中只能够有⼀个主进程来广播服务器的状态变更,因此能够很好地处理客户端大量的并发请求。 但是,也要考虑到主进程在任何时候都有可能出现崩溃退出或重启现象,因此,ZAB协议还需要做到当前主进程当出现上述异常情况的时候,依旧能正常工作。
ZAB协议的核心是定义了对于那些会改变Zookeeper服务器数据状态的事务请求的处理方式。
即:所有事务请求必须由一个全局唯⼀的服务器来协调处理,这样的服务器被称为Leader服务器,余下的服务器则称为Follower服务器,Leader服务器负责将⼀个客户端事务请求转化成⼀个事务Proposal(提议)
,并将该Proposal分发给集群中所有的Follower服务器,之后Leader服务器需要等待所有Follower服务器的反馈,⼀旦超过半数的Follower服务器进行了正确的反馈后,那么Leader就会再次向所有的Follower服务器分发Commit消息,要求其将前⼀个Proposal进行提交。
ZAB协议包括两种基本的模式:崩溃恢复
和消息广播
进入崩溃恢复模式:
当整个服务框架启动过程
中,或者是Leader服务器出现网络中断
、崩溃退出
或重启
等异常情况时,ZAB协议就会进入崩溃恢复模式,同时选举产生新的Leader服务器。当选举产生了新的Leader服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式,其中,所谓的状态同步就是指数据同步,用来保证集群中过半的机器能够和Leader服务器的数据状态保持⼀致。
进入消息广播模式:
当集群中已经有过半的Follower服务器
完成了和Leader服务器的状态同步,那么整个服务框架就可以进入消息广播模式,当⼀台同样遵守ZAB协议的服务器启动后加入到集群中,如果此时集群中已经存在⼀个Leader服务器在负责进行消息广播,那么加入的服务器就会自觉地进入数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后⼀起参与到消息广播流程中去。Zookeeper只允许唯⼀的⼀个Leader服务器来进行事务请求的处理,Leader服务器在接收到客户端的事务请求后,会生成对应的事务提议并发起⼀轮广播协议,而如果集群中的其他机器收到客户端的事务请求后,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。
接下来我们就重点讲解一下ZAB协议的消息广播过程和崩溃恢复过程
ZAB协议的消息广播过程使用原子广播协议,类似于一个二阶段提交过程,针对客户端的事务请求,Leader服务器会为其生成对应的事务Proposal
,并将其发送给集群中其余所有的机器,然后再分别收集各自的选票,最后进行事务提交。
在ZAB的二阶段提交过程中,移除了中断逻辑,所有的Follower服务器要么正常反馈Leader提出的事务Proposal,要么就抛弃Leader服务器,同时,ZAB协议将二阶段提交中的中断逻辑移除意味着我们可以在过半的Follower服务器已经反馈Ack之后就开始提交事务Proposal了,而不需要等待集群中所有的Follower服务器都反馈响应,但是,在这种简化的二阶段提交模型下,无法处理因Leader服务器崩溃退出而带来的数据不一致问题,因此ZAB采用了崩溃恢复模式来解决此问题,另外,整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易保证消息广播过程中消息接受与发送的顺序性。
在整个消息广播过程中,Leader服务器会为每个事务请求生成对应的Proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务Proposal分配一个全局单调递增的唯一ID,称之为事务ID(ZXID)
,由于ZAB协议需要保证每个消息严格的因果关系,因此必须将每个事务Proposal按照其ZXID的先后顺序来进行排序和处理。
具体的过程:在消息广播过程中,Leader服务器会为每一个Follower服务器都各自分配一个单独的队列,然后将需要广播的事务 Proposal 依次放入这些队列中去,并且根据 FIFO策略进行消息发送。每一个Follower服务器在接收到这个事务Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给Leader服务器一个Ack响应。当Leader服务器接收到超过半数Follower的Ack响应后,就会广播一个Commit消息给所有的Follower服务器以通知其进行事务提交,同时Leader自身也会完成对事务的提交,而每一个Follower服务器在接收到Commit消息后,也会完成对事务的提交。
ZAB协议的这个基于原子广播协议的消息广播过程,在正常情况下运行非常良好,但是一旦在Leader服务器出现崩溃,或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。在ZAB协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的Leader服务器,因此,ZAB协议需要一个高效且可靠的Leader选举算法,从而保证能够快速地选举出新的Leader,同时,Leader选举算法不仅仅需要让Leader自身知道已经被选举为Leader,同时还需要让集群中的所有其他机器也能够快速地感知到选举产生出来的新Leader服务器。
根据上⾯的内容,我们了解到,ZAB协议规定了如果⼀个事务Proposal在⼀台机器上被处理成功,那么应该在所有的机器上都被处理成功,哪怕机器出现故障崩溃。接下来我们看看在崩溃恢复过程中,可能会出现的两个数据不⼀致性的隐患及针对这些情况ZAB协议所需要保证的特性。
(1)ZAB协议需要确保那些已经在Leader服务器上提交的事务最终被所有服务器都提交
假设⼀个事务在 Leader 服务器上被提交了,并且已经得到过半 Folower 服务器的Ack反馈,但是在它将Commit消息发送给所有Follower机器之前,Leader服务器挂了,如图所示:
图中的消息C2就是一个典型的例子:在集群正常运行过程中的某一个时刻,Server1 是 Leader 服务器,其先后广播了消息 P1、P2、C1、P3 和 C2,其中,当Leader服务器将消息C2(C2是Commit Of Proposal2的缩写,即提交事务Proposal2)发出后就立即崩溃退出了。针对这种情况,ZAB协议就需要确保事务Proposal2最终能够在所有的服务器上都被提交成功,否则将出现不一致。
(2)ZAB协议需要确保丢弃那些只在Leader服务器上被提出的事务
如果在崩溃恢复过程中出现一个需要被丢弃的提案,那么在崩溃恢复结束后需要跳过该事务Proposal,如图所示:
在图所示的集群中,假设初始的 Leader 服务器 Server1 在提出了一个事务Proposal3 之后就崩溃退出了,从而导致集群中的其他服务器都没有收到这个事务Proposal3。于是,当 Server1 恢复过来再次加入到集群中的时候,ZAB 协议需要确保丢弃Proposal3这个事务。
结合上面提到的这两个崩溃恢复过程中需要处理的特殊情况,就决定了ZAB协议必须设计这样一个Leader 选举算法:能够确保提交已经被 Leader 提交的事务 Proposal,同时丢弃已经被跳过的事务Proposal。针对这个要求,如果让Leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最大编号(即ZXID最大)的事务Proposal,那么就可以保证这个新选举出来的Leader一定具有所有已经提交的提案。更为重要的是,如果让具有最高编号事务Proposal 的机器来成为 Leader,就可以省去 Leader 服务器检查Proposal的提交和丢弃工作的这一步操作了。
完成Leader选举之后,在正式开始工作(即接收客户端的事务请求,然后提出新的提案)之前,Leader服务器会首先确认事务日志中的所有Proposal是否都已经被集群中过半的机器提交了,即是否完成数据同步。下面我们就来看看ZAB协议的数据同步过程。
所有正常运行的服务器,要么成为 Leader,要么成为 Follower 并和 Leader 保持同步。Leader服务器需要确保所有的Follower服务器能够接收到每一条事务Proposal,并且能够正确地将所有已经提交了的事务Proposal应⽤到内存数据库中去。具体的,Leader服务器会为每一个Follower服务器都准备一个队列
,并将那些没有被各Follower服务器同步的事务以Proposal消息的形式逐个发送给Follower服务器,并在每一个Proposal消息后面紧接着再发送一个Commit消息,以表示该事务已经被提交。等到Follower服务器将所有其尚未同步的事务 Proposal 都从 Leader 服务器上同步过来并成功应用到本地数据库中后,Leader服务器就会将该Follower服务器加入到真正的可用Follower列表中,并开始之后的其他流程。
在ZAB协议的设计中,每个进程都有可能处于如下三种状态之一
所有进程初始状态都是LOOKING状态,此时不存在Leader,接下来,进程会试图选举出一个新的Leader,之后,如果进程发现已经选举出新的Leader了,那么它就会切换到FOLLOWING状态,并开始和Leader保持同步,处于FOLLOWING状态的进程称为Follower,LEADING状态的进程称为Leader,当Leader崩溃或放弃领导地位时,其余的Follower进程就会转换到LOOKING状态开始新一轮的Leader选举。
一个Follower只能和一个Leader保持同步,Leader进程和所有的Follower进程之间都通过心跳检测机制来感知彼此的情况。若Leader能够在超时时间内正常收到心跳检测,那么Follower就会一直与该Leader保持连接,而如果在指定时间内Leader无法从过半的Follower进程那里接收到心跳检测,或者TCP连接断开,那么Leader会放弃当前周期的领导,并转换到LOOKING状态,其他的Follower也会选择放弃这个Leader,同时转换到LOOKING状态,之后会进行新一轮的Leader选举。
联系:
区别:
Paxos算法中,新选举产生的主进程会进行两个阶段的工作,第一阶段称为读阶段,新的主进程和其他进程通信来收集主进程提出的提议,并将它们提交。第二阶段称为写阶段,当前主进程开始提出自己的提议。
ZAB协议在Paxos基础上添加了同步阶段,此时,新的Leader会确保 存在过半的Follower已经提交了之前的Leader周期中的所有事务Proposal。这一同步阶段的引入,能够有效地保证Leader在新的周期中提出事务Proposal之前,所有的进程都已经完成了对之前所有事务Proposal的提交。
总的来说,ZAB协议和Paxos算法的本质区别在于,两者的设计目标不太一样,ZAB协议主要用于构建一个高可用的分布式数据主备系统,而Paxos算法则用于构建一个分布式的一致性状态机系统。
Leader服务器是Zookeeper集群工作的核心,其主要工作有以下两个:
请求处理链
使用责任链来处理每个客户端的请求是Zookeeper的特色,Leader服务器的请求处理链如下:
可以看到,从prepRequestProcessor到FinalRequestProcessor前后一共7个请求处理器组成了leader服务器的请求处理链。
Follower服务器是Zookeeper集群状态中的跟随者,其主要工作有以下三个:
和leader⼀样,Follower也采用了责任链模式组装的请求处理链来处理每⼀个客户端请求,由于不需要对事务请求的投票处理,因此Follower的请求处理链会相对简单,其处理链如下:
和 Leader 服务器的请求处理链最大的不同点在于,Follower 服务器的第⼀个处理器换成了FollowerRequestProcessor处理器,同时由于不需要处理事务请求的投票,因此也没有了ProposalRequestProcessor处理器。
(1) FollowerRequestProcessor
其用作识别当前请求是否是事务请求,若是,那么Follower就会将该请求转发给Leader服务器,Leader服务器在接收到这个事务请求后,就会将其提交到请求处理链,按照正常事务请求进行处理。
(2) SendAckRequestProcessor
其承担了事务日志记录反馈的角色,在完成事务日志记录后,会向Leader服务器发送ACK消息以表明自身完成了事务日志的记录工作。
Observer是ZooKeeper自3.3.0版本开始引入的⼀个全新的服务器角色。从字⾯意思看,该服务器充当了⼀个观察者的角色——其观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来。Observer服务器在工作原理上和Follower基本是一致的,对于非事务请求,都可以进行独立的处理,而对于事务请求,则会转发给Leader服务器进行处理。和Follower唯一的区别在于,Observer不参与任何形式的投票,包括事务请求Proposal的投票和Leader选举投票。简单地讲,Observer服务器只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
另外,Observer的请求处理链路和Follower服务器也非常相近,其处理链如下:
另外需要注意的⼀点是,虽然在图中可以看到,Observer 服务器在初始化阶段会将
SyncRequestProcessor处理器也组装上去,但是在实际运行过程中,Leader服务器不会将事务请求的投票发送给Observer服务器。