15 | ZAB协议:如何实现操作的顺序性?

很多同学应该使用过 ZooKeeper,它是一个开源的分布式协调服务,比如你可以用它进行配置管理、名字服务等等。在 ZooKeeper 中,数据是以节点的形式存储的。如果你要用 ZooKeeper 做配置管理,那么就需要在里面创建指定配置,假设创建节点"/geekbang"和"/geekbang/time",步骤如下:

[zk: 192.168.0.10:2181(CONNECTED) 0] create /geekbang 123
Created /geekbang
[zk: 192.168.0.10:2181(CONNECTED) 1] create /geekbang/time 456
Created /geekbang/time

我们分别创建了配置"/geekbang"和"/geekbang/time",对应的值分别为 123 和 456。那么在这里我提个问题:你觉得在 ZooKeeper 中,能用兰伯特的 Multi-Paxos 实现各节点数据的共识和一致吗?

当然不行。因为兰伯特的 Multi-Paxos,虽然能保证达成共识后的值不再改变,但它不关心达成共识的值是什么,也无法保证各值(也就是操作)的顺序性。而这就是 Zookeeper 没有采用 Multi-Paxos 的原因,又是 ZAB 协议着力解决的,也是你理解 ZAB 协议的关键。

那么为了更好地理解这个协议,接下来,将分别以如何实现操作的顺序性、领导者选举、故障恢复、处理读写请求为例,具体讲解一下。希望能在全面理解 ZAB 协议的同时,加深对 Paxos 算法的理解。

今天这节,会从 ZAB 协议的最核心设计目标(如何实现操作的顺序性)出发,了解它的基础原理。

老规矩,在开始今天的内容之前,我们先来看一道思考题:

假如节点 A、B、C 组成的一个分布式集群,我们要设计一个算法,来保证指令(比如 X、Y)执行的顺序性,比如,指令 X 在指令 Y 之前执行,那么我们该如何设计这个算法呢?

带着这个问题,我们进入今天的内容。

为什么 Multi-Paxos 无法保证操作顺序性?

刚刚提到“Multi-Paxos 无法保证操作的顺序性”。为了让你真正理解这个问题,我举个具体的例子演示一下(为了演示方便,我们假设当前所有节点上的被选定指令,最大序号都为 100,那么新提议的指令对应的序号就会是 101)。

首先节点 A 是领导者,提案编号为 1,提议了指令 X、Y,对应的序号分别为 101 和 102,但是因为网络故障,指令只成功复制到了节点 A。

假设这时节点 A 故障了,新当选的领导者为节点 B。节点 B 当选领导者后,需要先作为学习者了解目前已被选定的指令。节点 B 学习之后,发现当前被选定指令的最大序号为 100(因为节点 A 故障了,它被选定指令的最大序号 102,无法被节点 B 发现),那么它可以从序号 101 开始提议新的指令。这时它接收到客户端请求,并提议了指令 Z,指令 Z 被成功复制到节点 B、C。

假设这时节点 B 故障了,节点 A 恢复了,选举出领导者 C 后,节点 B 故障也恢复了。节点 C 当选领导者后,需要先作为学习者了解目前已被选定的指令,这时它执行 Basic Paxos 的准备阶段,就会发现之前选定的值(比如 Z、Y),然后发送接受请求,最终在序号 101、102 处达成共识的指令是 Z、Y。就像下图的样子。

在这里,可以看到,原本预期的指令是 X、Y,最后变成了 Z、Y。讲到这儿,你应该可以知道,为什么用 Multi-Paxos 不能达到我们想要的结果了吧?

这个过程,其实很明显的验证了“Multi-Paxos 虽然能保证达成共识后的值不再改变,但它不关心达成共识的值是什么。”

那么咱们接着回到开篇的问题,假设在 ZooKeeper 中直接使用了兰伯特的 Multi-Paxos,这时创建节点"/geekbang"和"/geekbang/time",那么就可能出现,系统先创建了节点"/geekbang/time",这样肯定就出错了:

[zk: 192.168.0.10:2181(CONNECTED) 0] create /geekbang/time 456
Node does not exist: /geekbang/time

因为创建节点"/geekbang/time"时,找不到节点"/geekbang",所以就会创建失败。

在这里多说几句,除了 Multi-Paxos,兰伯特还有很多关于分布式的理论,这些理论都很经典(比如拜占庭将军问题),但也因为太早了,与实际场景结合的不多,所以后续的众多算法是在这个基础之上做了大量的改进(比如,PBFT、Raft 等)。关于这一点,我在 13 讲也强调过,你需要注意一下。

另外再延伸一下,其实在ZAB 论文中,关于 Paxos 问题(Figure 1 ​​​​)的分析是有争议的。因为 ZooKeeper 当时应该考虑的是 Multi-Paxos,而不是有多个提议者的 Basic Paxos。而在 Multi-Paxos 中,领导者作为唯一提议者,是不存在同时多个提议者的情况。也就是说,Paxos(更确切的说是 Multi-Paxos)无法保证操作的顺序性的问题是存在的,但原因不是 ZAB 论文中演示的原因,本质上是因为 Multi-Paxos 实现的是一系列值的共识,不关心最终达成共识的值是什么,不关心各值的顺序,就像我们在上面演示的过程那样。

那既然 Multi-Paxos 不行,ZooKeeper 怎么实现操作的顺序性的呢? 答案是它实现了 ZAB 协议。

你可能会说了:Raft 可以实现操作的顺序性啊,为什么 ZooKeeper 不用 Raft 呢?这个问题其实比较简单,因为 Raft 出来的比较晚,直到 2013 年才正式提出,在 2007 年开发 ZooKeeper 的时候,还没有 Raft 呢。

ZAB 是如何实现操作的顺序性的?

如果用一句话解释 ZAB 协议到底是什么,我觉得它是:能保证操作顺序性的,基于主备模式的原子广播协议。

接下来,还是以 X、Y 指令为例具体演示一下,帮助更好理解为什么 ZAB 能实现操作的顺序性(为了演示方便,我们假设节点 A 为主节点,节点 B、C 为备份节点)。

首先,需要注意的是,在 ZAB 中,写操作必须在主节点(比如节点 A)上执行。如果客户端访问的节点是备份节点(比如节点 B),它会将写请求转发给主节点。如图所示:

接着,当主节点接收到写请求后,它会基于写请求中的指令(也就是 X,Y),来创建一个提案(Proposal),并使用一个唯一的 ID 来标识这个提案。这里我说的唯一的 ID 就是指事务标识符(Transaction ID,也就是 zxid),就像下图的样子。

从图中你可以看到,X、Y 对应的事务标识符分别为 <1, 1> 和 <1, 2>,这两个标识符是什么含义呢?

可以这么理解,事务标识符是 64 位的 long 型变量,有任期编号 epoch 和计数器 counter 两部分组成(为了形象和方便理解,我把 epoch 翻译成任期编号),格式为 ,高 32 位为任期编号,低 32 位为计数器:

任期编号,就是创建提案时领导者的任期编号,需要你注意的是,当新领导者当选时,任期编号递增,计数器被设置为零。比如,前领导者的任期编号为 1,那么新领导者对应的任期编号将为 2。

计数器,就是具体标识提案的整数,需要你注意的是,每次领导者创建新的提案时,计数器将递增。比如,前一个提案对应的计数器值为 1,那么新的提案对应的计数器值将为 2。

为什么要设计的这么复杂呢?因为事务标识符必须按照顺序、唯一标识一个提案,也就是说,事务标识符必须是唯一的、递增的。

在创建完提案之后,主节点会基于 TCP 协议,并按照顺序将提案广播到其他节点。这样就能保证先发送的消息,会先被收到,保证了消息接收的顺序性。

看这张图,X 一定在 Y 之前到达节点 B、C。

然后,当主节点接收到指定提案的“大多数”的确认响应后,该提案将处于提交状态(Committed),主节点会通知备份节点提交该提案。

在这里,需要你注意的是,主节点提交提案是有顺序性的。主节点根据事务标识符大小,按照顺序提交提案,如果前一个提案未提交,此时主节点是不会提交后一个提案的。也就是说,指令 X 一定会在指令 Y 之前提交。

最后,主节点返回执行成功的响应给节点 B,节点 B 再转发给客户端。你看,这样我们就实现了操作的顺序性,保证了指令 X 一定在指令 Y 之前执行。

最后想补充的是,当写操作执行完后,接下来你可能需要执行读操作了。需要注意,为了提升读并发能力,Zookeeper 提供的是最终一致性,也就是读操作可以在任何节点上执行,客户端会读到旧数据:

如果客户端必须要读到最新数据,怎么办呢?Zookeeper 提供了一个解决办法,那就是 sync 命令。你可以在执行读操作前,先执行 sync 命令,这样客户端就能读到最新数据了:

[zk: 192.168.0.10:2181(CONNECTED) 2] sync /geekbang/time
[zk: 192.168.0.10:2181(CONNECTED) 3] Sync returned 0
[zk: 192.168.0.10:2181(CONNECTED) 3] get /geekbang/time
456
cZxid = 0x100000005
ctime = Mon Apr 20 21:19:28 HKT 2020
mZxid = 0x100000005
mtime = Mon Apr 20 21:19:28 HKT 2020
pZxid = 0x100000005
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
[zk: 192.168.0.10:2181(CONNECTED) 4]

内容小结

本节主要了解了为什么 Multi-Paxos 无法实现操作的顺序性,以及 ZAB 协议如何保证操作的顺序性。希望你明确这样几个重点。

1. 兰伯特的 Multi-Paxos 只考虑了如何实现共识,也就是如何就一系列值达成共识,未考虑如何实现各值(也就是操作)的顺序性。

2.ZAB 是通过“一切以领导者为准”的强领导者模型和严格按照顺序处理、提交提案,来实现操作的顺序性的。

那么说到 ZAB,很多同学可能有这样的疑问:为什么 ZAB 作者宣称ZAB 不是 Paxos 算法,但又有很多资料提到 ZAB 是 Multi-Paxos 算法呢?到底该怎么理解呢?

我的看法是,你可以把它理解为 Multi-Paxos 算法。因为技术是发展的,概念的内涵也在变化。Raft 算法(主备、强领导者模型)与 ZAB 协议非常类似,它是作为共识算法和 Multi-Paxos 算法提出的。当它被广泛接受和认可后,共识算法的内涵也就丰富和发展了,不仅能实现一系列值的共识,还能保证值的顺序性。同样,Multi-Paxos 算法不仅指代多次执行 Basic Paxos 的算法,还指代主备、强领导者模型的共识算法。

当然了,在学习技术过程中,我们不可避免的会遇到有歧义、有争议的信息,就像在 11 讲留言区中,有同学提到“从网上搜了搜相关资料,发现大部分资料将谣言传播等同于 Gossip 协议,也有把反熵等同于 Gossip 协议的,感到很迷惑”。

这就需要我们不仅要在平时工作和学习中,认真、全面的学习理论,掌握概念的内涵,还要能“包容”和“发展”着理解技术。

最后,在本节我们了解了 ZAB 协议的最核心设计目标(如何实现操作的顺序性),那么既然“所有数据都是以主节点的数据为准的”,主节点(也就是领导者)那么重要,那当它崩溃了,该怎么处理呢?下节,会重点了解这部分内容。

课堂思考

既然提到在 ZAB 协议中,主节点是基于 TCP 协议来广播消息的,并保证了消息接收的顺序性。那么你不妨想想,如果 ZAB 采用的是 UDP 协议,能保证消息接收的顺序性吗?为什么呢?

  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值