ZAB协议基本概念以及选举同步过程详解

概述

Google 的粗粒度锁服务 Chubby 的设计开发者 Burrows 曾经说过:“所有一致性协议本质上要么是 Paxos 要么是其变体”。Paxos 虽然解决了分布式系统中,多个节点就某个值达成一致性的通信协议。但是还是引入了其他的问题。由于其每个节点,都可以提议提案,也可以批准提案。当有三个及以上的 proposer 在发送 prepare 请求后,很难有一个 proposer 收到半数以上的回复而不断地执行第一阶段的协议,在这种竞争下,会导致选举速度变慢

所以 zookeeper 在 paxos 的基础上,提出了 ZAB 协议,本质上是,只有一台机器能提议提案(Proposer),而这台机器的名称称之为 Leader 角色。其他参与者扮演 Acceptor 角色。为了保证 Leader 的健壮性,引入了 Leader 选举机制。

ZAB协议还解决了这些问题

  1. 在半数以下节点宕机,依然能对台提供服务
  2. 客户端所有的写请求,交由 Leader 来处理。写入成功后,需要同步给所有的 follower 和 observer
  3. leader 宕机,或者集群重启。需要确保已经再 Leader 提交的事务最终都能被服务器提交,并且确保集群能快速回复到故障前的状态
图片[1]-ZAB协议基本概念以及选举同步过程详解-不念博客
ZAB协议

基本概念

  • 基本名词数据节点(dataNode):zk 数据模型中的最小数据单元,数据模型是一棵树,由斜杠( / )分割的路径名唯一标识,数据节点可以存储数据内容及一系列属性信息,同时还可以挂载子节点,构成一个层次化的命名空间。事务及 zxid:事务是指能够改变 Zookeeper 服务器状态的操作,一般包括数据节点的创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每个事务请求,zk 都会为其分配一个全局唯一的事务 ID,即 zxid,是一个 64 位的数字,高 32 位表示该事务发生的集群选举周期(集群每发生一次 leader 选举,值加 1),低 32 位表示该事务在当前选择周期内的递增次序(leader 每处理一个事务请求,值加 1,发生一次 leader 选择,低 32 位要清 0)。事务日志:所有事务操作都是需要记录到日志文件中的,可通过 dataLogDir 配置文件目录,文件是以写入的第一条事务 zxid 为后缀,方便后续的定位查找。zk 会采取“磁盘空间预分配”的策略,来避免磁盘 Seek 频率,提升 zk 服务器对事务请求的影响能力。默认设置下,每次事务日志写入操作都会实时刷入磁盘,也可以设置成非实时(写到内存文件流,定时批量写入磁盘),但那样断电时会带来丢失数据的风险。事务快照:数据快照是 zk 数据存储中另一个非常核心的运行机制。数据快照用来记录 zk 服务器上某一时刻的全量内存数据内容,并将其写入到指定的磁盘文件中,可通过 dataDir 配置文件目录。可配置参数 snapCount,设置两次快照之间的事务操作个数,zk 节点记录完事务日志时,会统计判断是否需要做数据快照(距离上次快照,事务操作次数等于snapCount/2~snapCount 中的某个值时,会触发快照生成操作,随机值是为了避免所有节点同时生成快照,导致集群影响缓慢)。
  • 核心角色leader:系统刚启动时或者 Leader 崩溃后正处于选举状态;follower:Follower 节点所处的状态,Follower 与 Leader 处于数据同步阶段;observer:Leader 所处状态,当前集群中有一个 Leader 为主进程。
  • 节点状态LOOKING:节点正处于选主状态,不对外提供服务,直至选主结束;FOLLOWING:作为系统的从节点,接受主节点的更新并写入本地日志;LEADING:作为系统主节点,接受客户端更新,写入本地日志并复制到从节点

ZAB协议 常见的误区

  • 写入节点后的数据,立马就能被读到,这是错误的。** zk 写入是必须通过 leader 串行的写入,而且只要一半以上的节点写入成功即可。而任何节点都可提供读取服务。例如:zk,有 1~5 个节点,写入了一个最新的数据,最新数据写入到节点 1~3,会返回成功。然后读取请求过来要读取最新的节点数据,请求可能被分配到节点 4~5 。而此时最新数据还没有同步到节点4~5。会读取不到最近的数据。如果想要读取到最新的数据,可以在读取前使用 sync 命令**。
  • zk启动节点不能偶数台,这也是错误的。zk 是需要一半以上节点才能正常工作的。例如创建 4 个节点,半数以上正常节点数是 3。也就是最多只允许一台机器 down 掉。而 3 台节点,半数以上正常节点数是 2,也是最多允许一台机器 down 掉。4 个节点,多了一台机器的成本,但是健壮性和 3 个节点的集群一样。基于成本的考虑是不推荐的

ZAB协议 选举同步过程

发起投票的契机

  1. 节点启动
  2. 节点运行期间无法与 Leader 保持连接,
  3. Leader 失去一半以上节点的连接

如何保证事务

ZAB 协议类似于两阶段提交,客户端有一个写请求过来,例如设置 /my/test 值为 1,Leader 会生成对应的事务提议(proposal)(当前 zxid为 0x5000010 提议的 zxid 为Ox5000011),现将set /my/test 1(此处为伪代码)写入本地事务日志,然后set /my/test 1日志同步到所有的follower。follower收到事务 proposal ,将 proposal 写入到事务日志。如果收到半数以上 follower 的回应,那么广播发起 commit 请求。follower 收到 commit 请求后。会将文件中的 zxid ox5000011 应用到内存中。

上面说的是正常的情况。有两种情况。第一种 Leader 写入本地事务日志后,没有发送同步请求,就 down 了。即使选主之后又作为 follower 启动。此时这种还是会日志会丢掉(原因是选出的 leader 无此日志,无法进行同步)。第二种 Leader 发出同步请求,但是还没有 commit 就 down 了。此时这个日志不会丢掉,会同步提交到其他节点中。

服务器启动过程中的投票过程

现在 5 台 zk 机器依次编号 1~5

  1. 节点 1 启动,发出去的请求没有响应,此时是 Looking 的状态
  2. 节点 2 启动,与节点 1 进行通信,交换选举结果。由于两者没有历史数据,即 zxid 无法比较,此时 id 值较大的节点 2 胜出,但是由于还没有超过半数的节点,所以 1 和 2 都保持 looking 的状态
  3. 节点 3 启动,根据上面的分析,id 值最大的节点 3 胜出,而且超过半数的节点都参与了选举。节点 3 胜出成为了 Leader
  4. 节点 4 启动,和 1~3 个节点通信,得知最新的 leader 为节点 3,而此时 zxid 也小于节点 3,所以承认了节点 3 的 leader 的角色
  5. 节点 5 启动,和节点 4 一样,选取承认节点 3 的 leader 的角色

服务器运行过程中选主过程

1.节点 1 发起投票,第一轮投票先投自己,然后进入 Looking 等待的状态 2.其他的节点(如节点 2 )收到对方的投票信息。节点 2 在 Looking 状态,则将自己的投票结果广播出去(此时走的是上图中左侧的 Looking 分支);如果不在 Looking 状态,则直接告诉节点 1 当前的 Leader 是谁,就不要瞎折腾选举了(此时走的是上图右侧的 Leading/following 分支) 3.此时节点 1,收到了节点 2 的选举结果。如果节点 2 的 zxid 更大,那么清空投票箱,建立新的投票箱,广播自己最新的投票结果。在同一次选举中,如果在收到所有节点的投票结果后,如果投票箱中有一半以上的节点选出了某个节点,那么证明 leader 已经选出来了,投票也就终止了。否则一直循环

zookeeper 的选举,优先比较大 zxid,zxid 最大的节点代表拥有最新的数据。如果没有 zxid,如系统刚刚启动的时候,则比较机器的编号,优先选择编号大的

同步的过程

在选出 Leader 之后,zk 就进入状态同步的过程。其实就是把最新的 zxid 对应的日志数据,应用到其他的节点中。此 zxid 包含 follower 中写入日志但是未提交的 zxid 。称之为服务器提议缓存队列 committedLog 中的 zxid。

同步会完成三个 zxid 值的初始化。

peerLastZxid:该 learner 服务器最后处理的 zxid。 minCommittedLog:leader服务器提议缓存队列 committedLog 中的最小 zxid。 maxCommittedLog:leader服务器提议缓存队列 committedLog 中的最大 zxid。 系统会根据 learner 的peerLastZxid和 leader 的minCommittedLog,maxCommittedLog做出比较后做出不同的同步策略

直接差异化同步

场景:peerLastZxid介于minCommittedLogZxid和maxCommittedLogZxid间

此种场景出现在,上文提到过的,Leader 发出了同步请求,但是还没有 commit 就 down 了。 leader 会发送 Proposal 数据包,以及 commit 指令数据包。新选出的 leader 继续完成上一任 leader 未完成的工作。

例如此刻Leader提议的缓存队列为 0x20001,0x20002,0x20003,0x20004,此处learn的peerLastZxid为0x20002,Leader会将0x20003和0x20004两个提议同步给learner

先回滚在差异化同步/仅回滚同步

此种场景出现在,上文提到过的,Leader写入本地事务日志后,还没发出同步请求,就down了,然后在同步日志的时候作为learner出现。

例如即将要 down 掉的 leader 节点 1,已经处理了 0x20001,0x20002,在处理 0x20003 时还没发出提议就 down 了。后来节点 2 当选为新 leader,同步数据的时候,节点 1 又神奇复活。如果新 leader 还没有处理新事务,新 leader 的队列为,0x20001, 0x20002,那么仅让节点 1 回滚到 0x20002 节点处,0x20003 日志废弃,称之为仅回滚同步。如果新 leader 已经处理 0x30001 , 0x30002 事务,那么新 leader 此处队列为0x20001,0x20002,0x30001,0x30002,那么让节点 1 先回滚,到 0x20002 处,再差异化同步0x30001,0x30002。

全量同步

peerLastZxid小于minCommittedLogZxid或者leader上面没有缓存队列。leader直接使用SNAP命令进行全量同步

© 版权声明
THE END