Zab协议,它的全称是“ZooKeeper Atomic Broadcast”,是ZooKeeper的原子组播协议,它是一种特别为ZooKeeper设计的一致性协议。
什么叫“原子组播”?就是不仅将Leader的状态变更以Proposal的形式广播到所有Follower去,还保证同一个Leader发起的消息按顺序被commit、FIFO,它的核心是zxid。有一种组播协议叫可靠组播(Reliable broadcast),它有多种组播协议的实现方式,但前提是在一个不出现故障的分布式系统中。而如果是在一个存在故障或有可能出现故障的分布式系统中,如何解决分布式共识问题呢?FLP不可能性证明,没有任何算法可以在存在故障的分布式系统中达成共识。FLP定理阻止了无数分布式设计者把时间浪费在寻找一个完美的理想的分布式共识算法上,而是更应该去使用一种有实际意义的算法,比如我们可以考虑让大多数节点达成一致的算法,就是考虑部分系统还是全部然后通过崩溃恢复让那些未达成一致的节点数据能够同步,这是否可接受呢?事实上,很多一致性协议都是这么做的,Zab也如此。Leader节点负责将数据的状态变更转换成一个提议Proposal,并分发给集群中的Follower节点,而后Leader节点收集该请求的响应,一旦超半数Follower节点进行了正确的响应后,那么Leader节点就会再次向集群中的Follower节点分发Commit消息,要求将Proposal进行提交。这和2PC(2阶段提交)不同,2PC提交协议的第二阶段汇总,需要所有参与者响应才会提交事务,只要有一个超时或异常响应,都需要中断或回滚事务。
在整个消息广播的过程中,Leader节点会为每个Proposal分配一个全局单调递增的zxid,它将作为唯一ID标识这个Proposal。在Zab协议的设计中,zxid是一个64位的数字,低32位可以看作是一个简单的单调递增的计数器,是当次epoch内的ID;高32位代表了Leader周期epoch的编号。每当Leader选举时,就会从这个Leader节点上取出其本地副本(日志)中最大的zxid,并从该zxid中解析出对应的epoch值,然后对其进行加1,之后就会以此zxid作为新的epoch,并将低32位置为0来开始生成新的zxid。如果让Leader选举算法能够保证新选举出来的Leader拥有集群中所有节点ZXID最大的Proposal,那么就可以保证这个新选举出来的Leader—定具有所有已经提交的Proposal。同时,如果让具有最高编号Proposal的机器来成为Leader, 就可以省去Leader检查Proposal的提交和丢弃工作的这一步操作。
zxid有什么用呢?
- 约束消息的顺序:Zab保证了同一个Leader发起的消息按顺序被commit、FIFO,因为Leader节点会为每一个Follower节点都准备一个单独的队列,然后将需要广播的Proposal依次放入,根据FIFO策略进行消息发送,通过zxid标识;
- 数据同步:当一个包含了上一个epoch周期中尚未提交过的Proposal的Follower节点加入到集群中或是Follower节点故障恢复,这意味着需要处理那些被丢弃的Proposal。此时集群中已存在的Leader会根据自身最后被提交的Proposal来和该Follower节点的Proposal进行比对,如果不同步,Leader 会要求 Follower 进行一个回退操作,回退到一个确实已经被超半数节点提交的最新的 Proposal;
Zab协议工作流程可以分解成discovery、sync、broadcast三个阶段:
- discovery: 选举产生Leader,然后收集Follower的epoch,根据Follower的反馈Leader产生一个新的epoch;
- sync: Follower同步多数派缺失的状态;
- broadcast: Leader处理Client的写操作,并将状态变更广播至Follower,Follower多数派通过之后Leader发起将状态变更落地(deliver/commit);