gitbook/分布式协议与算法实战/docs/229975.md
2022-09-03 22:05:03 +08:00

161 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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之前执行那么我们该如何设计这个算法呢
![](https://static001.geekbang.org/resource/image/55/c9/55dc6f6bf822db027858b8b4fdb89cc9.jpg "图1")
带着这个问题,我们进入今天的内容。
## 为什么Multi-Paxos无法保证操作顺序性
刚刚我提到“Multi-Paxos无法保证操作的顺序性”。为了让你真正理解这个问题我举个具体的例子演示一下为了演示方便我们假设当前所有节点上的被选定指令最大序号都为100那么新提议的指令对应的序号就会是101
首先节点A是领导者提案编号为1提议了指令X、Y对应的序号分别为101和102但是因为网络故障指令只成功复制到了节点A。
![](https://static001.geekbang.org/resource/image/3e/f3/3ed203904d35eaf0bd5162262c1b4ef3.jpg "图2")
假设这时节点A故障了新当选的领导者为节点B。节点B当选领导者后需要先作为学习者了解目前已被选定的指令。节点B学习之后发现当前被选定指令的最大序号为100因为节点A故障了它被选定指令的最大序号102无法被节点B发现那么它可以从序号101开始提议新的指令。这时它接收到客户端请求并提议了指令Z指令Z被成功复制到节点B、C。
![](https://static001.geekbang.org/resource/image/77/12/77f94aa5dec044ffc1a61547cb10f112.jpg "图3")
假设这时节点B故障了节点A恢复了选举出领导者C后节点B故障也恢复了。节点C当选领导者后需要先作为学习者了解目前已被选定的指令这时它执行Basic Paxos的准备阶段就会发现之前选定的值比如Z、Y然后发送接受请求最终在序号101、102处达成共识的指令是Z、Y。就像下图的样子。
![](https://static001.geekbang.org/resource/image/e6/c4/e6c27d879025ef6943a5985da1351bc4.jpg "图4")
在这里你可以看到原本预期的指令是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论](https://www.semanticscholar.org/paper/Zab:-High-performance-broadcast-for-primary-backup-Junqueira-Reed/b02c6b00bd5dbdbd951fddb00b906c82fa80f0b3)文中关于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它会将写请求转发给主节点。如图所示
![](https://static001.geekbang.org/resource/image/77/6f/770c39b4ea339799bc3ca4a0b0d8266f.jpg "图5")
接着当主节点接收到写请求后它会基于写请求中的指令也就是XY来创建一个提案Proposal并使用一个唯一的ID来标识这个提案。这里我说的唯一的ID就是指事务标识符Transaction ID也就是zxid就像下图的样子。
![](https://static001.geekbang.org/resource/image/d0/9b/d0063fa9275ce0a114ace27db326d19b.jpg "图6")
从图中你可以看到X、Y对应的事务标识符分别为<1, 1><1, 2>,这两个标识符是什么含义呢?
你可以这么理解事务标识符是64位的long型变量有任期编号epoch和计数器counter两部分组成为了形象和方便理解我把epoch翻译成任期编号格式为<epoch, counter>高32位为任期编号低32位为计数器
* 任期编号就是创建提案时领导者的任期编号需要你注意的是当新领导者当选时任期编号递增计数器被设置为零。比如前领导者的任期编号为1那么新领导者对应的任期编号将为2。
* 计数器就是具体标识提案的整数需要你注意的是每次领导者创建新的提案时计数器将递增。比如前一个提案对应的计数器值为1那么新的提案对应的计数器值将为2。
为什么要设计的这么复杂呢?因为事务标识符必须按照顺序、唯一标识一个提案,也就是说,事务标识符必须是唯一的、递增的。
在创建完提案之后主节点会基于TCP协议并按照顺序将提案广播到其他节点。这样就能保证先发送的消息会先被收到保证了消息接收的顺序性。
![](https://static001.geekbang.org/resource/image/e5/96/e525af146900c892e0c002affa77d496.jpg "图7")
你看这张图X一定在Y之前到达节点B、C。
然后当主节点接收到指定提案的“大多数”的确认响应后该提案将处于提交状态Committed主节点会通知备份节点提交该提案。
![](https://static001.geekbang.org/resource/image/1d/19/1d3950b6d91845789cce1f0569969419.jpg "图8")
在这里需要你注意的是主节点提交提案是有顺序性的。主节点根据事务标识符大小按照顺序提交提案如果前一个提案未提交此时主节点是不会提交后一个提案的。也就是说指令X一定会在指令Y之前提交。
最后主节点返回执行成功的响应给节点B节点B再转发给客户端。**你看这样我们就实现了操作的顺序性保证了指令X一定在指令Y之前执行。**
最后我想补充的是当写操作执行完后接下来你可能需要执行读操作了。你需要注意为了提升读并发能力Zookeeper提供的是最终一致性也就是读操作可以在任何节点上执行客户端会读到旧数据
![](https://static001.geekbang.org/resource/image/d4/1c/d405381e5fad12730149baa4fae63e1c.jpg "图9")
如果客户端必须要读到最新数据怎么办呢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算法](https://cwiki.apache.org/confluence/display/ZOOKEEPER/Zab+vs.+Paxos)但又有很多资料提到ZAB是Multi-Paxos算法呢到底该怎么理解呢
我的看法是你可以把它理解为Multi-Paxos算法。因为技术是发展的概念的内涵也在变化。Raft算法主备、强领导者模型与ZAB协议非常类似它是作为共识算法和Multi-Paxos算法提出的。当它被广泛接受和认可后共识算法的内涵也就丰富和发展了不仅能实现一系列值的共识还能保证值的顺序性。同样Multi-Paxos算法不仅指代多次执行Basic Paxos的算法还指代主备、强领导者模型的共识算法。
当然了在学习技术过程中我们不可避免的会遇到有歧义、有争议的信息就像在11讲留言区中有同学提到“从网上搜了搜相关资料发现大部分资料将谣言传播等同于Gossip协议也有把反熵等同于Gossip协议的感到很迷惑”。
这就需要我们不仅要在平时工作和学习中,认真、全面的学习理论,掌握概念的内涵,还要能“包容”和“发展”着理解技术。
最后在本节课我们了解了ZAB协议的最核心设计目标如何实现操作的顺序性那么既然“所有数据都是以主节点的数据为准的”主节点也就是领导者那么重要那当它崩溃了该怎么处理呢下节课我会重点带你了解这部分内容。
## 课堂思考
既然我提到在ZAB协议中主节点是基于TCP协议来广播消息的并保证了消息接收的顺序性。那么你不妨想想如果ZAB采用的是UDP协议能保证消息接收的顺序性吗为什么呢欢迎在留言区分享你的看法与我一同讨论。
最后,感谢你的阅读,如果这节课让你有所收获,也欢迎你将它分享给更多的朋友。
* * *
编辑角目前课程已经结束了为了交付更好的内容《分布式协议与算法实战》于2020年4.26日启动迭代计划15讲为迭代版本后面也会对ZAB协议进行更细致化的讨论敬请期待