分布式一致性之 ZAB

总结一下 zab 协议。

基本概念

在 ZAB 协议中,每个消息都被赋予了一个 zxid,zxid 全局唯一。zxid 有两部分组成:高32位是 epoch,低 32 位是 epoch 内的自增 id,由 0 开始。每次选出新的 Leader,epoch 会递增,同时 zxid 的低 32 位清 0。ZAB 协议中,消息的编号只能由 Leader 节点来分配。每个节点都对应一个唯一 id。

系统中节点的状态:

  • looking: 节点正处于选主状态,不对外提供服务。
  • following: 作为系统的从节点,接受主节点的更新并写入本地日志。
  • leading: 作为系统主节点,接受客户端更新写入本地日志并复制到从节点。

消息广播

ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个 二阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作。广播可以分为三过程:

  1. 将数据都复制到 Follwer 中。
  2. 等待 Follwer 回应 Ack,最低超过半数即成功。
  3. 当超过半数成功回应,则给每一个 follower 发送 commit 通知,同时提交自己。

崩溃恢复

Leader 节点运行后会周期性地向 Follower 发送心跳信息,如果一个 Follower 未收到 Leader 节点的心跳信息,Follower 节点的状态会从 following 转变为 looking。Leader 节点也会检测 Follower 节点的状态,如果多数 Follower 节点不再响应 Leader 节点,那么 Leader 也会重新发起一次新的选主。

当一个节点收到一个投票请求时,首先会比较 zid,对方 zid 比自己大就投票,如果相等则比较 myid,对方 myid 更大就投给对方,否则就拒绝投票,如果有 Follower 收到超过半数的投票则变为 Leader。

思考一个问题: leader 收到合法数量 Follower 的 ack 后,就向各个 Follower 广播 commit 命令,同时也会在本地执行 commit 并向 client返回 success。但是如果在各个 follower 在收到 commit 命令前 leader 就挂了,导致剩下的服务器并没有执行都这条消息。

由于一个 proposal 如果被 commit 那一定会得到 majority follower 的 ack 回复,即大多数已经将该 proposal 写入日志文件。根据上面的原则,新的 leader 一定包含被 leader commit 的 proposal。接下来,新 leader 与 follower 建立先进先出的队列, 先将自身有而 follower 缺失的 proposal 发送给它,再将这些 proposal 的 commit 命令发送给 follower。ps: 实际要复杂一点,为了解决一些覆盖问题,会先执行一个 new-leader 操作提交一个基于当前 epoch 的空日志,这个操作等于 raft 的 no-op 操作,感兴趣的朋友可以阅读一下 raft paper 的 5.4.2。