type
status
date
slug
summary
tags
category
icon
password

1、概述

Zab 协议是为 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议 ,是 Zookeeper 保证数据一致性的核心算法。Zab 借鉴了 Paxos 算法,但又不像 Paxos 那样,是一种通用的分布式一致性算法。它是特别为 Zookeeper 设计的支持崩溃恢复的原子广播协议。
基于该协议,Zookeeper 实现了一种主备模型(即Leader和Follower模型)的系统架构来保证集群中各个副本之间数据的一致性。 这里的主备系统架构模型,就是指只有一台客户端(Leader)负责处理外部的写事务请求,然后 Leader 客户端将数据同步到其他 Follower 节点。
Zookeeper 客户端会随机的链接到 Zookeeper 集群中的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向 Leader 提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交。
Zab 还要保证同一个 Leader 发起的事务要按顺序被执行,同时还要保证只有先前 Leader 的事务被执行之后,新选举出来的 Leader 才能再次发起事务。比如 P1 的事务 t1 可能是创建节点 /a,t2 可能是创建节点 /a/bb,只有先创建了父节点 /a,才能创建子节点 /a/b

2、Zab协议内容

2.1 Zab协议的角色

ZAB 协议定义了三种角色,每个服务器承担如下三种角色中的一种:
  • Leader:集群的核心,一个ZooKeepe r集群同一时间只会有一个实际工作的 Leader,工作内容:
    • 发起并维护与各 Follwer 及 Observer 间的心跳。
    • 所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器。
  • Follower:Leader 的追随者,一个ZooKeeper集群可能同时存在多个Follower,工作内容:
    • 响应Leader的心跳。
    • 直接处理并返回客户端的读请求。
    • 将写请求转发给 Leader 处理,并且负责在 Leader 处理写请求时对请求进行投票。
    • 参与 Leader 选举投票
  • Observer:为了提高集群写性能而引入的新角色。因为写操作需要(通常)需要集群中至少一半的节点投票达成一致,因此随着更多投票者的加入,写入性能也会随着下降。Observer 不参与事务请求 Proposal 的投票,也不参与 Leader 选举投票,其他功能和 Follower 完全相同。所以我们可以引入任意数量的Observer,同时不会影响我们集群的写入性能。工作内容有:
    • 响应Leader的心跳。
    • 直接处理并返回客户端的读请求。
    • 将写请求转发给 Leader处理,但只听取投票结果,不会参与投票过程。
每个服务器通过自身的状态来区分自己的角色,在运行期间可能会出现以下状态之一:
  • LOOKING:寻找 Leader 服务的状态,处于当前状态后,将会进行 Leader 选举流程
  • FOLLOWING:代表当前服务端处于跟随者状态,表明是 Follower 角色
  • LEADING:代表当前服务端处于领导者状态,表明是 Leader 角色
  • OBSERVING:观察者状态,表明是 Observer 角色

2.2 Zab协议的核心原理

Zab 协议分为两种模式:
  • 崩溃恢复:在进入崩溃恢复模式时 Zookeeper 集群会进行 Leader 选举,一般有两种情况会进入该模式:
    • 当服务器启动时期会进行 Leader 选举。
    • 当服务器运行期 Leader 服务器的出现网络中断、奔溃退出、重启等异常情况,或者当集群中半数的服务器与该 Leader 服务器无法通信时,进入崩溃恢复模式,开始 Leader 选举。
  • 消息广播:当选举产生了新的 Leader,同时集群中有过半的机器与该 Leader 服务器完成了状态同步(即数据同步)之后,Zab协议就会退出崩溃恢复模式,进入消息广播模式,开始接收处理客户端的请求。这个模式有点类似于 2PC。

    2.3 崩溃恢复模式

    崩溃恢复主要包括两部分:Leader选举数据恢复

    2.3.1 Leader选举

    一旦 Leader 服务器出现崩溃或者由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系,那么就会进入崩溃恢复模式。余下的非 Observer 服务器都会将自己的服务器状态变更为 LOOKING,然后开始进入 Leader 选举流程。
    1. 自增选举轮次。每个服务器在开始新一轮投票时,会先对自己维护的 logicClock 进行自增操作。
    1. 初始化投票。每个服务器在广播自己的选票前,会将自己的投票箱清空,生成选票信息,然后先把票投给自己。生成的选票有以下两种情况:
      1. 在服务启动的初始阶段没有 zxid 值,就会发送(myid,0)。
      2. 在服务器运行期间,每个服务器上的 zxid 都有值,且 zxid 都不相同,所以就正常发送(myid,zxid)。
    1. 发送投票。把(myid,zxid)作为广播信息发送给所有参与投票的服务器(除了Observer),然后等待集群中的服务器返回信息。
    1. 处理投票。当前服务器收到其他服务器发来的选票,记入自己的投票箱内。先判断回复节点此时的运行状态:
      1. 回复节点是 LOOKING 状态,说明对方也在选举中,当前服务器需要将别人的投票和自己的投票进行PK
        1. electionEpoch 比较:
          1. 如果收到选票的 electionEpoch 小,直接忽略此选票。
          2. 如果收到选票的 electionEpoch 和当前相同,那么认为是合法的选票,接着判断是否应该更新选票。
          3. 如果收到选票的 electionEpoch 更大,说明自己所保存的逻辑时钟落伍了,使用收到选票的electionEpoch更新本机逻辑时钟,然后清空收到的选票信息,更新提议信息(这里有一个判断过程),重新发送更新后的提议。
        2. 选票pk:假如 A 是当前服务器收到的选票,B 是当前服务器本身的选票,当以下三个条件满足其一时,将接受 A 选票的提议,并重新发送选票信息。
          1. election epoch(A) > election epoch (B)
          2. zxid(A) > zxid(B)
          3. sid(A) > sid(B)
      2. 回复节点是 LEADING/FOLLOWING 状态,说明集群目前有 Leader,只需要确保当前服务器和 Leader 能够正常通信,并收到了集群半数以上服务器推荐推荐此 Leader时,就直接加入到集群中去。
        1. electionEpoch 比较:
          1. 如果逻辑时钟 Epoch 相同,将该数据保存到 recvset,如果所接收服务器宣称自己是 Leader,那么将判断是不是有半数以上的服务器选举它,如果是则设置选举状态退出选举过程。
          2. 否则这是一条与当前逻辑时钟不符合的消息,那么说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到 outofelection 集合中,再根据 outofelection 来判断是否可以结束选举,如果可以也是保存逻辑时钟,设置选举状态,退出选举过程。
    1. 统计投票:每次投票后,服务器都会统计所有投票,判断是否已经有过半的机器接收到相同的投票信息。所谓“过半”就是指大于集群机器数量的一半,即大于或等于 (n/2+1)。对于这里由 3 台机器构成的集群,大于等于2台即为达到“过半”要求。达到过半要求表示已经已经选出了 Leader。
    1. 更新服务器状态:一旦确定了 Leader,每个服务器就会更新自己的状态:如果是 Follower,那么就变更为 FOLLOWING,如果是 Leader,那么就变更为 LEADING。
    notion image
    选举过程中的相关概念:
    • zxid:Leader 服务器在接收到事务请求后,会为每个事务请求生成对应的 Proposal 来进行广播,并且在广播事务 Proposal 之前,Leader 服务器会首先为这个事务 Proposal 分配一个全局单调递增的唯一 ID ,我们称之为事务 ID(即 ZXID)。一个 zxid 是一个全局有序的 64 位的数字,可以分为两个部分:
      • 高 32 位:epoch(纪元),理解为当前集群所处的年代,或者周期。每当选举产生一个新的 Leader 服务器时就会取出其本地日志中最大事务的 ZXID ,解析出 epoch(纪元)值操作加 1 作为新的 epoch ,并将低 32 位置零。所以每经过一次 leader 选举产生一个新的 leader,新 leader 会将 epoch 号 +1。
      • 低 32 位:counter(计数器),它是一个简单的单调递增的计数器,针对客户端的每个事务请求都会进行加 1 操作。新 leader 选举后这个值重置为 0。
    • sid:也称作 myid,服务器id。每个 ZooKeeper 服务器都需要在数据文件夹下创建一个名为 myid 的文件,该文件包含整个 ZooKeeper 集群唯一的ID(整数)。在配置文件中 ID 与hostname必须一一对应server. 后面的数据即为 myid= 后面为 hostname,例如 server.1=zoo1:2888:3888,服务器的 myid1hostnamezoo1
    • electionEpoch:每次 leader 选举,electionEpoch就会自增1,统计选票信息时,首先保证 electionEpoch 相同。
    • logicClock:每个服务器会维护一个自增的整数,名为 logicClock,它表示这是该服务器发起的第多少轮投票。
    • recvset:用来记录选票信息,以方便后续统计。
    • outofelection:用来记录选举逻辑之外的选票,例如当一个服务器加入 zookeeper集群 时,因为集群已经存在,不用重新选举,只需要在满足一定条件下加入集群即可。

    2.3.2 数据恢复

    完成 Leader 选举后,在正式开始工作之前,Leader 服务器会首先确认事务日志中的所有的 Proposal 是否已经被集群中过半的服务器 Commit。对比的原理是将 Follower 的最新的日志 zxid 和 Leader 的已经提交的日志 zxid 对比,会有以下几种可能:
    • 如果 Leader 的最新提交的日志 zxid 比 Follower 的最新日志的 zxid 大,那就将多的 Proposal 发送给 Follower,让他补齐。
    • 如果 Leader 的最新提交的日志 zxid 比 Follower 的最新日志的 zxid 小,那就发送命令给 Follower,要求 Follower 进行一个回退操作,回退到一个确实已经被集群中过半机器 Commit 的最新 Proposal。
    • 如果两者恰好一样,那什么都不用做。

    2.4 消息广播模式

    进入消息广播模式后,所有服务器开始接收客户端的请求。
    1. 客户端发起一个写操作请求。
    1. Leader 服务器将客户端的请求转化为事务 Proposal 提案,同时为每个 Proposal 分配一个全局的ID,即 zxid。
    1. Leader 服务器为每个 Follower 服务器分配一个单独的队列,然后将需要广播的 Proposal 依次放到队列中,并且根据 FIFO 策略进行消息发送,保证了 Follower 接收到的 Proposal 提案是按照 zxid 大小顺序发出的。
    1. Follower 接收到 Proposal 后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向 Leader 反馈一个 Ack 响应消息。
    1. Leader 接收到超过半数以上 Follower 的 Ack 响应消息后,即认为消息发送成功,可以发送 commit 消息。
    1. Leader 向所有 Follower 广播 commit 消息,同时自身也会完成事务提交。Follower 接收到 commit 消息后,会将上一条事务提交。

    2.5 异常处理

    Zab 协议中可能发生的两个异常:
    • 一个事务在 Leader 上提交了,并且过半的 Folower 都响应 Ack 了,但是 Leader 在 Commit 消息发出之前挂了。
    • 一个事务在 Leader 提出之后,Leader 挂了。
    要确保如果发生上述两种情况,数据还能保持一致性,那么 Zab 协议选举算法必须满足以下要求:
    1. 已经被 Leader commit 的 Proposal 必须最终被所有的 Follower 服务器提交。解决流程:
      1. 选举拥有 proposal 最大值(即 zxid 最大) 的节点作为新的 Leader:由于所有提案被 COMMIT 之前必须有合法数量的 follower ACK,即必须有合法数量的服务器的事务日志上有该提案的 proposal,因此,zxid 最大也就是数据最新的节点保存了所有被 commit 消息的 proposal 状态。
      2. 新的 Leader 将自己事务日志中 proposal 但未 COMMIT 的消息处理。
      3. 新的 Leader 与 Follower 建立先进先出的队列, 先将自身有而 Follower 没有的 proposal 发送给 Follower,再将这些 proposal 的 commit 命令发送给 Follower,以保证所有的 Follower 都保存了所有的 proposal、所有的 Follower 都处理了所有的消息。通过以上策略,能保证已经被处理的消息不会丢。
    1. Leader 提出的但是没有被 commit 的 Proposal 要被丢弃。解决流程:
      1. 当旧的 Leader 作为 Follower 接入新的 Leader 后,新的 Leader 会让它将所有的拥有旧的 epoch 号的未被 commit 的 proposal 清除。
    Zookeeper笔记分布式一致性算法:Raft算法
    mcbilla
    mcbilla
    一个普通的干饭人🍚
    Announcement
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    🎉欢迎来到飙戈的博客🎉
    -- 感谢您的支持 ---
    👏欢迎学习交流👏