15分钟全面掌握zookeeper-zab协议

781 阅读8分钟

ZAB协议介绍

基本概念

ZAB(ZooKeeper Atomic Broadcast)ZooKeeper原子消息广播协议。ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。

在ZooKeeper中,主要依赖ZAB协议来实现分布式数据一致性,基于该协议,ZooKeeper实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性。

具体的,ZooKeeper使用一个单一的主进程来接收并处理客户端的所有事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事务Proposal的形式广播到所有的副本进程上去。

考虑到主进程在任何时候都有可能出现崩溃退出或重启现象,因此,ZAB协议还需要做到在当前主进程出现上述异常情况的时候,依旧能够正常工作,也就是崩溃恢复。

ZAB大体流程:

  • 所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为 Leader服务器,
  • 余下的其他服务器则成为 Follower 服务器。
  • Leader 服务器负责将一个客户端事务请求转换成一个事务 Proposal(提议),并将该 Proposal 分发给集群中所有的Follower 服务器。
  • Leader 服务器需要等待所有 Follower 服务器的反馈,等待超过半数的 Follower 服务器进行了正确的反馈
  • Leader 就会再次向所有的 Follower服务器分发Commit消息,要求其将前一个Proposal进行提交。

关键词:主从集群模式、分布式事务、ZAB、二阶段提交

ZAB协议包括两种基本的模式,分别是消息广播和崩溃恢复

消息广播

上面流程已经介绍了消息广播模式的流程,类似于一个二阶段提交过程。

image-20210628124137915.png

此处 ZAB 协议中涉及的二阶段提交过程则与其略有不同。在 ZAB 协议的二阶段提交过程中,移除了中断逻辑,所有的 Follower 服务器要么正常反馈 Leader 提出的事务 Proposal,要么就抛弃Leader服务器。同时,ZAB协议将二阶段提交中的中断逻辑移除意味着我们可以在过半的 Follower 服务器已经反馈 Ack 之后就开始提交事务 Proposal 了,而不需要等待集群中所有的Follower服务器都反馈响应。一句话过半集群响应即可继续执行事务请求。

举个例子来说,一个由3台机器组成的ZAB服务,通常由1个Leader、2个Follower服务器组成。某一个时刻,假如其中一个 Follower服务器挂了,仍旧会将Commit广播,整个 ZAB 集群是不会中断服务,这是因为Leader服务器依然能够获得过半机器(包括Leader自己)的支持。

在整个消息广播过程中,Leader服务器会为每个事务请求生成对应的Proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务Proposal分配一个全局单调递增的唯一ID,我们称之为事务ID(即ZXID);因此可以利用递增特性保证消息处理的先后顺序,这也是ZK特性之一FIFO。

顺便提一句,如果集群中的其他机器接收到客户端的事务请求,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。

具体实现

具体的,在消息广播过程中,Leader服务器会为每一个Follower服务器都各自分配一个单独的队列,然后将需要广播的事务 Proposal 依次放入这些队列中去,并且根据 FIFO策略进行消息发送。每一个Follower服务器在接收到这个事务Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给Leader服务器一个Ack响应。当Leader服务器接收到超过半数Follower的Ack响应后,就会广播一个Commit消息给所有的Follower服务器以通知其进行事务提交,同时Leader自身也会完成对事务的提交,而每一个Follower服务器在接收到Commit消息后,也会完成对事务的提交。

崩溃恢复

当整个服务框架在启动过程中,或是当Leader服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的Leader服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致。

基本特性:ZAB协议规定了如果一个事务Proposal在一台机器上被处理成功,那么应该在所有的机器上都被处理成功。这是如何做到的呢?

完成Leader选举之后,在正式开始工作(即接收客户端的事务请求,然后提出新的提案)之前,Leader服务器会首先确认事务日志中的所有Proposal是否都已经被集群中过半的机器提交了,即是否完成数据同步。接下来看看数据同步过程

数据同步

Leader服务器会为每一个Follower服务器都准备一个队列,并将那些没有被各Follower服务器同步的事务以Proposal消息的形式逐个发送给Follower服务器,并在每一个Proposal消息后面紧接着再发送一个Commit消息。等到Follower服务器将所有其尚未同步的事务 Proposal 都从 Leader 服务器上同步过来并成功应用到本地数据库中后,Leader服务器就会将该Follower服务器加入到真正的可用Follower列表中。如何判断Follower服务器事务是否需要同步呢?这就依靠ZXID的特性。

ZAB协议的事务编号ZXID,ZXID是一个64位的数字,低32位代表事务编号,单调递增每次加一;高32位则代表了Leader周期epoch的编号,每当选举产生一个新的Leader服务器,就会从这个Leader服务器上取出其本地日志中最大事务Proposal的ZXID,并从该ZXID中解析出对应的epoch值,然后再对其进行加1操作,然后将低32位从0开始重新计数。再利用高32对即将成为的Follower服务器的低32比较,就可以同步事务一致性。

对ZAB探讨完后,数据恢复中会有Leader选举过程,紧接着继续详细说明Zookeeper选举

Zookeeper选举

​ 选举主要分为两个阶段:服务器启动、服务器运行期间的Leader选举。

服务器启动期间选举

  1. 每个Server发出一个投票

    由于是初始情况,因此对于Server1和Server2来说,都会将自己作为Leader服务器来进行投票,每次投票包含的最基本的元素包括:所推举的服务器的 myid 和ZXID,我们以(myid,ZXID)的形式来表示。因为是初始化阶段,因此无论是Server1还是Server2,都会投给自己,即Server1的投票为(1,0),Server2的投票为(2,0),然后各自将这个投票发给集群中其他所有机器。

  2. 接收来自各个服务器的投票

    每个服务器都会接收来自其他服务器的投票。集群中的每个服务器在接收到投票后,首先会判断该投票的有效性,包括检查是否是本轮投票、是否来自LOOKING状态的服务器。

  3. 处理投票

    在接收到来自其他服务器的投票后,针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK的规则如下。

    • 优先检查ZXID。ZXID比较大的服务器优先作为Leader。
    • 如果ZXID相同的话,那么就比较myid。myid比较大的服务器作为Leader服务器。
  4. 统计投票

​ 每次投票后,服务器都会统计所有投票,判断是否已经有过半的机器接收到相同的投票信息。“过半”就是指大于集群机器数量的一半,即大于或等于(n/2+1)

  1. 改变服务器状态 一旦确定了 Leader,每个服务器就会更新自己的状态:如果是 Follower,那么就变更为FOLLOWING,如果是Leader,那么就变更为LEADING。

服务器运行期间的Leader选举

当Leader所在的机器挂了,那么整个集群将暂时无法对外服务,而是进入新一轮的Leader选举。服务器运行期间的Leader选举和启动时期的Leader选举基本过程是一致的。

  1. 变更状态。

    当 Leader 挂了之后,余下的非 Observer 服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举流程。

  2. 每个Server会发出一个投票 在这个过程中,需要生成投票信息(myid,ZXID)。因为是运行期间,因此每个服务器上的ZXID可能不同,我们假定Server1的ZXID为123,而Server3的ZXID为 122。在第一轮投票中,Server1 和 Server3 都会投自己,即分别产生投票(1,123)和(3,122),然后各自将这个投票发给集群中所有机器。

  3. 以下步骤跟启动时期的Leader选举过程是一致的。