ZAB协议

232 阅读9分钟

ZAB协议

ZAB协议简介

ZAB协议是Fast Paxos算法中的一种工业实现算法。ZAB,即Zookeeper Atomic Broadcastzk原子消息广播协议,是专为Zookeeper设计的一种支持崩溃恢复的原子广播协议,在Zookeeper中,主要依赖ZAB协议来实现分布式数据的一致性。

Zookeeper使用一个单一进程来接受并处理客户端的所有事务请求(写请求),当服务器数据的状态发生变更后,集群中采用ZAB原子广播协议,以事务提案Proposal的形式广播到所有的副本进程上,ZAB协议能够保证一个全局的变更序列,即可以为每一个事务分配一个全局的递增编号xid

Zookeeper客户端连接到Zookeeper集群的一个节点后,若客户端提交的是读请求,那么当前节点就直接根据自己保存的数据对其进行响应(LeaderFollowerObserver都是可以处理读请求的);如果是写请求并且当前节点不是Leader,那么节点就会将写请求转发给LeaderLeader会以提案的方式广播该写操作,只要超过半数节点同意该写操作,则该写操作请求就会被提交,然后Leader会再次广播给所有订阅者,即Learner,通知它们同步数据。

image.png

三类角色

为了避免Zookeeper的单点问题,zk也是以集群的形式出现的,zk集群中的角色主要有以下三类:

  • Leaderzk集群写请求的唯一处理者,并且负责进行投票的发起和决议,更新系统状态。Leader是很民主的,并不是说其在接受到写请求之后马上就修改其中保存的数据,而是首先根据写请求提出一个提案,在大都数zkServer均同意的情况下才会做出修改。

  • Follower:接受客户端请求,处理读请求,并向客户端返回结果,将写请求转发给Leader在选Leader和决议Leader发出的写操作的Proposal中参与投票,注意,Follower是具有投票权的。

  • Observer:可以理解为无选主投票权与写操作投票权的Server,其不属于法定人数范畴,主要是为了协助Follower处理更多的读请求。

以上三类角色在不同的情况下又有一些不同的叫法。

  • Learner:学习者,要从Leader中同步数据的Server,在集群处于正常服务状态时,FollowerObserver相对于Leader来说,都是LearnerLearner = Follower+Observer

  • QuorumServer:法定服务器,具有投票权限的主机。在Leader选举过程中,各个参与选举与被选举的主机都称为QuorumServerQuorumServer不包含Observer,在选举结束后,这些QuorumServer中会产生一个Leader,其他的就成为了Follower了,QuorumServer = Leader + Follower

三种模式

ZAB协议中对zkServer的状态描述有三种模式:恢复模式、同步模式和广播模式,这三种模式并没有十分明显的界限,它们相互交织在一起。

  • 恢复模式:在服务器重启过程中,或者在Leader崩溃后,就进入到了恢复模式,恢复模式主要包含两个阶段:Leader选举阶段和初始化同步阶段,这两个阶段完成之后zk集群恢复到了正常的服务状态。

  • 广播模式:广播模式分类两类:初始化广播和更新广播。当Leader被选举出来之后,Leader需要将自己拥有但是其他Server可能没有的事务及自己的epoch广播给所有的Learner,这是初始化广播。在集群正常服务时,当Leader的事务提案被大多数Follower同意之后,Leader会修改自身数据,然后会将修改后的数据广播给其他的Learner,这就是更新广播。

  • 同步模式:同步模式也分为两类:初始化同步和更新同步,跟广播模式相对于,只不过广播模式是站在Leader角度,而同步模式是站在Learner角度。当Leader选举出来并发布过其初始化广播之后,所有的Learner需要将Leader广播的事务以及epoch同步到本地,这就是初始化同步。在集群正常服务时,当Leader发布了更新广播之后,所有的Learner需要将Leader广播的事务同步到本地,这就是更新同步。

三个数据

Zookeeper中有三个很重要的数据:zxidepochxid

  • zxidzxid64位长度的long类型,其中高32位表示纪元epoch,低32位表示事务标识xid。即xid由两部分构成epochxid

  • epoch:每一个Leader都会具有一个不同的epoch值,表示一个时期、时代,每一次新的选举结束之后生成一个新的epoch,新的Leader产生,则会更新所有的zkServerzxid中的epoch

  • xidxid则为zk事务的id,每一个写操作都是一个事务,都会有一个xidxid为一个依次递增的流水号,每一个写操作都需要由Leader发起一个提案,由所有的Follower表决是否同意本次写操作,而每个提案都具有一个zxid

同步模式与广播模式

初始化同步

当完成Leader选举之后,此时的Leader是准Leader,其要进入到回复模式下的初始化同步阶段,将自己有的数据而其他FollowerObserver没有的同步给他们,使准Leader转变为真正的Leader,保证数据的一致性。初始化同步的原理图如下:

image.png

  • 1.为了保证LeaderLearner发送提案的顺序性,Leader会每一个Learner服务器准备一个队列。
  • 2.Leader将那些没有被各个Learner服务器同步的事务封装成为Proposal
  • 3.Leader将这些Proposal逐条发送给各个Learner服务器,并在每一个Proposal后紧跟一个COMMIT消息,表示事务已经被提交,Learner可以直接接收并执行。
  • 4.Learner接收到来自LeaderProposal,并将其更新到本地。
  • 5.当Follower更新成功之后,会向准Leader发送ACK反馈。
  • 6.Leader服务器在收到来自FollowerACK之后就会将该Follower加入到真正可用的Follower队列中。

上图中为什么Learner3没有向准Leader发送ACK呢?因为Learner3ObserverObserver是没有反馈功能的

消息广播算法

如果集群中的其他节点收到客户端的事务请求,那么这些Learner会将写请求转发给Leader服务器,然后再执行如下的具体过程:注意下面的FollowersQueuesObserverQueues两个队列在上面的初始化同步中已经创建出

image.png

  • 1.Leader接收到事务请求之后,为事务赋予一个全局唯一的64位自增id,即zxid,通过zxid的大小比较即可实现事务的有序性管理,然后将事务封装为一个Proposal
  • 2.Leader根据Follower列表获取到所有的Follower,然后再将Proposal通过Follower队列将提案发送给各个Follower
  • 3.当Follower接收到提案后。会先将提案的zxid与本地记录的事务日志中的最大的zxid进行比较,若当前提案的zxid大于最大的zxid,则将当前提案记录到本地事务日志中,并向Leader返回一个ACK.
  • 4.当Leader接收到超过半数的ACKS之后,Leader就会向所有的Follower队列发送COMMIT消息,向所有的Observer队列发送Proposal
  • 5.当Follower接收到COMMIT消息之后,就会将日志中的事务正式更新到本地。当Observer接收到Proposal之后,会直接将事务更新到本地。

恢复模式两个原则

已被处理过的消息不能丢

image.png

如上图所示,当Leader接收到超过半数FollowerACK之后,就向各个FollowerA,B,C,D广播COMMIT消息,批准各个Server执行该写操作事务,当各个Server在接收到LeaderCOMMIT消息后就会在本地执行写操作,然后向客户端响应写操作是否执行成功。

但是此时假设只有AB接收到了COMMIT消息后,并在本地执行了更新操作,而CD在还没有接收到COMMIT消息之前Leader就挂了,这将导致一种后果:部分的ServerAB)已经执行了该事务,而部分ServerCD)尚未接收到COMMIT消息,所以没有在本地执行该事务。

接着进行新的Leader选举,此时Leader肯定会在AB中产生。为什么一定会在在AB中产生呢?

因为AB中执行了最新的事务,而CD没有,说明AB中的事务标识xid肯定比CD的大,并且此时A,B,C,Depoch肯定一样的。所以ABzxid肯定比CD大的。我们知道进行Leader选举时,首先比较zxid,选取zxid最大的当选,如果zxid一样,再接着比较myidServer的标识。

假设AB中的A本选取当了Leader之后,开始进行初始化同步操作,就会把自己有而其他主机没有的数据进行同步,此时原来Leader宕机前的最后一个事务也会被同步到CD中,就保证了已被处理过的消息不会丢失。

被丢弃的消息不能再现

image.png

如上图所示,当Leader接收到事务请求并生成了Proposal,但是还没有向A,B,C,D四个Follower发送时就挂了,此时所有的Follower都不知道该Proposal的存在。

接着进行新的Leader选举,假设此时A被当选为新的Leader,整个集群进入到正常服务状态后,之前挂了的Leader主机重新启动并注册成为了Follower,不要忘了该主机上还存在着不为人知的Proposal,那么其数据就会比其他主机多了内容,导致整个系统状态的不一致,所以,该Proposal就被消除。

那么如何将其清除呢?根据epoch生成规则,新的LeaderAepoch值一定会大于当前集群中任何主机记录的事务的zxid值(确切的说。是大于zxid的高32位),当新的Leader产生后,会将它的epoch广播给所有的Learner,并且会将所有Learner中所有的旧的未被COMMIT过的Proposal清除。