共计 4114 个字符,预计需要花费 11 分钟才能阅读完成。
这篇文章给大家介绍 Zookeeper 一致性协议 Zab 如何理解,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
zookeeper 是分布式协调系统,用来协调、同步多服务器之间的状态,容错能力强
一个应用要保证 HA,往往需要 N 个服务器(N 1) 提供服务,其中有 M 台 master,N- M 台 slave。这样一台挂了,另外 N - 1 台也能提供服务。所以,数据也会备份成 N 份散布在这些服务器上。现在的问题变成了,如何管理这 N 台服务器?如何在 master 节点失败的时候重新选择 master?如何保证所有服务器存储的备份数据一致?
如果你碰到上面那些棘手的问题,zookeeper 刚好可以帮到你:
存储公共配置信息
跟踪所有进程运行状态
集群中各节点的管理
master 选主
提供分布式同步支持(分布式锁等功能)
上面列出的是官方提供的功能支持,还有很多其他功能等待挖掘。
当你的系统依赖 zookeeper 保证 HA 和一致性的时候,你肯定也好奇,zookeeper 本身是如何保证这两个特性的。幕后功臣往往容易被忽视,其实它就是 Zookeeper 原子广播协议(Zab 协议)。如果你了解过二阶段提交、paxos 算法、raft 算法等大名鼎鼎的分布式一致性算法,Zab 肯定也不会陌生,因为他们要达到的目的相同,就是保证应用程序的 HA 和一致性。
为了读懂后面的内容,有些术语需要了解:
Zab 的理论和实现并不完全一致。
实现是在理论的基础之上做了一些优化手段,这些优化是在 zookeeper 不断发展的过程中给加上的。我接下来的讲解,也是参照这篇论文,以 zookeeper 3.3.3 的版本的实现为准,并用自己的语言总结。
理论中的协议
随着系统启动或者恢复,会经历 Zab 协议中描述的如下四个阶段
阶段 0:Leader 选举。每个 peer 从 Quorum peer 中选出自己心中的预备 leader
阶段 1:发现。预备 leader 从 Quorum Follower 中发现最新的数据,并覆盖自己的过期数据
阶段 2:同步。预备 leader 采用二阶段提交的方式将自己的最新数据同步给 Quorum Follower。完成这个步骤,预备 leader 就转为正式 leader
阶段 3:广播。Leader 接受写请求,并通过二阶段提交的方式广播给 Quorum Follower
之前我只是简述了一下理论中的协议,然而理想很骨感,有很多需要改进或者妥协的地方。下面我会一一阐明:
阶段 0 的选举 leader 实际上很简陋,只是“看对眼”了就选为预备 leader,所以预备 leader 的数据可能并非最新
预备 leader 数据过期,就需要用阶段 1 来弥补,通过互相传输数据,来发现最新的数据,并完成预备 leader 的数据升级
更多的网络间数据传输代表了更大的网络开销
协议实现
了解了理想的骨感之后,我们回归现实。
真正 apache zookeeper 在实现上提出设想:优化 leader 选举,直接选出最新的 Peer 作为预备 Leader,这样就能将阶段 0 和阶段 1 合并,减少网络上的开销和多流程的复杂性
由图可知,代码实现上,协议被简化为三个阶段
快速选举 Leader 阶段:从 Quorum Peer 中选出数据最新的 peer 作为 leader
恢复阶段:Leader 将数据同步给 Quorum Follower
广播阶段:Leader 接受写请求,并广播给 Quorum Follower
Talk is cheap.Show me the Code
这时 Linus 说过的一句话,无论语言多么有力,只有代码才能真正展现作者的思想。之前我只是对是协议实现的三个阶段做了一番简述,只有代码才能真正拨开 Zab 协议外面那层迷雾。(为了不浪费篇幅,这里采用伪代码)
快速选举 Leader 阶段(FLE)
首先初始化一些数据
ReceivedVotes:投票箱,存储投票节点以及当前投票信息
OutOfElection:存储已经成为 Leader、Follower,不参与投票的节点信息,以及它的历史投票信息
在选票中写上自己的大名
send notification:发起选票通知,该节点会携带选票,进入目标节点的队列中,相当于给自己投了一票,并毛遂自荐给其他人
如果当前节点处于选举状态,则它也会接到选票通知。它会从队列中不断轮询节点,以便获取选票信息(如果超时,则不断放松超时时间,直至上限)。根据轮询出来的发送节点的状态,来做相应的处理。
election:如果发送节点的轮次(round)大于自己,说明自己的选举信息过时,则更新自己的选举轮次,清空投票箱,更新自己的选票内容,并将新的选票通知给其他节点;如果发送节点的轮次等于自己,并且投票内容比自己的更新,则只需要更新自己的选票,并通知给其他节点就行;如果发送节点的轮次小于自己,说明投票内容过期,没有参考意义,直接忽略。所有未被忽略的选票,都会进入投票箱。最终根据选票箱中的结果,判断当前节点的选票是不是占大多数,如果是就根据当前节点的选票选出 Leader
leading 或 following:发送节点轮次等于自己,说明发送节点还参与投票,如果发送节点是 Leading 或者它的选票在选票箱中占大多数,则直接完成选举;如果发送节点已经完成选举(轮次不同)或者它收集的选票较少,那么它的信息都会存放在 OutOfElection 中。当节点不断完成选举,OutOfElection 中数量逐渐变成 Quorum 时,就把 OutOfElection 当做投票箱,从中检查发送节点的选票是否占多数,如果是就直接选出 Leader
恢复阶段
经过 FLE,已经选出了日志中具有最新已提交的事务的节点作为预备 Leader。下面就分 Leader 和 Follower 两个视角来介绍具体实现。
Leader 视角
首先,更新 lastZxid,将纪元 +1,计数清零,宣布改朝换代啦。然后在每次接收到 Follower 的数据同步请求时,都会将自己 lastZxid 反馈回去,表示所有 Follower 以自己的 lastZxid 为准。接下来,根据具体情况来判断该如何将数据同步给 Follower
如果 Leader 的历史提交事务比 Follower 的最新事务要新,说明 Follower 的数据有待更新。更新方式取决于,Leader 最早事务有没有比 Follower 最新事务要新:如果前者更新,说明在 Leader 看来 Follower 所有记录的事务都太过陈旧,没有保留价值,这时只需要将 Leader 所有 history 发给 Follower 就行(响应 SNAP);如果后者更新,说明在 Leader 看来,Follower 从自己的 lastZxid 开始到 Leader 日志的最新事务,都需要同步,于是将这一部分截取并发送给 Follower(响应 DIFF)
如果 Leader 的历史提交事务没有 Follower 的最新事务新,说明 Follower 存在没有提交的事务,这些事务都应该被丢弃(响应 TRUNC)
当 Follower 完成同步时,会发送同步 ack,当 Leader 收到 Quorum ack 时,表示数据同步阶段大功告成,进入最后的广播阶段。
Follower 视角
通知 Leader,表示自己希望能同步 Leader 中的数据。
当收到 Leader 的拒绝响应时,说明 Leader 不承认自己作为 Follower,有可能该 Leader 并不可靠,于是开始重新开始 FLE
当收到 SNAP 或 DIFF 响应时,Follower 会同步 Leader 发送过来的事务
当收到 TRUNC 响应,Follower 会丢弃所有未完成的数据
当每个 Follower 完成上述的同步过程时,会发送 ack 给 Leader,并进入广播阶段。
广播阶段
进入到这个阶段,说明所有数据完成同步,Leader 已经转正。开始 zookeper 最常见的工作流程:广播。
广播阶段是真正接受事务请求(写请求)的阶段,也代表了 zookeeper 正常工作阶段。所有节点都能接受客户端的写请求,但是 Follower 会转发给 Leader,只有 Leader 才能将这些请求转化成事务,广播出去。这个节点一样有两个角色,下面还是按照这两个角色来讲解。
Leader 视角:
Leader 必须经过 ready,才能接受写请求。完成 ready 的 Leader 不断接受写请求,转化成事务请求,广播给 Quorum Follower。
当 Leader 接收到 ack 时,说明 Follower 完成相应处理,Leader 广播提交请求,Follower 完成提交。
当发现新 Peer 请求作为 Follower 加入时,将自己的纪元、事务日志发送给该 Peer,以便它完成上述恢复阶段的过程。收到该 Peer 的同步完成的 ack 时,Leader 会发送提交请求,以便 Peer 提交所有同步完成的事务。这时,该 Peer 转正为 Follower,被 Leader 纳入 Quorum Follower 中。
Follower 视角:
Follower 被发现是 Leading 状态,则执行 ready 过程,用来接受写请求。
当接受到 Leader 广播过来的事务请求时,Follower 会将事务记录在 history,并响应 ack。
当接收到 Leader 广播过来的提交请求时,Follower 会检查 history 中有没有尚未提交的事务,如果有,需要等待之前的事务按照 FIFO 顺序提交之后,才能提交本事务。
文章没有介绍 Zookeeper 的使用,而是着重讲解它的核心协议 Zab 的实现。正如文中提及,Zab 最早的设想和现在的实现并不相同,今日的实现是在 Zookeeper 不断发展壮大的过程中不断优化、改进而来的,也许早期的实现就是 yahoo 论文中构想的那样。罗马不是一日建成,任何人都不能指望一口吃个大胖子。如果 Zookeeper 刚开始就想着如何优化到极致,那反而会严重影响到这个项目本身的价值,因为它很可能还没面试就被淘汰。
可以看出,过早优化是万恶之源。但是同时,一个好的程序员也不会忘记需要优化的那部分,他会定位相应的代码,然后针对性的修改。这也是 zookeeper 的开发者所做的。
关于 Zookeeper 一致性协议 Zab 如何理解就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。