You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 13 | PBFT算法有人作恶如何达成共识
你好,我是韩健。
学完了[01讲](https://time.geekbang.org/column/article/195662)的拜占庭将军问题之后,有同学在留言中表达了自己的思考和困惑:口信消息型拜占庭问题之解在实际项目中是如何落地的呢?先给这位同学点个赞,很棒!你能在学习的同时思考落地实战。
不过事实上,它很难在实际项目落地,因为口信消息型拜占庭问题之解是一个非常理论化的算法,没有和实际场景结合,也没有考虑如何在实际场景中落地和实现。
比如,它实现的是在拜占庭错误场景下,忠将们如何在叛徒干扰时,就一致行动达成共识。但是它并不关心结果是什么,这会出现一种情况:现在适合进攻,但将军们达成的最终共识却是撤退。
很显然这不是我们想要的结果。因为在实际场景中我们需要就提议的一系列值而不是单值即使在拜占庭错误发生的时候也能被达成共识。那你要怎么做呢答案就是掌握PBFT算法。
PBFT算法非常实用是一种能在实际场景中落地的拜占庭容错算法它在区块链中应用广泛比如Hyperledger Sawtooth、Zilliqa。为了帮助你更好地理解PBFT算法在今天的内容中我除了带你了解PBFT达成共识的原理之外还会介绍口信消息型拜占庭问题之解的局限。相信学习完本讲内容后你不仅能理解PBFT达成共识的基本原理还能理解算法背后的演化和改进。
老规矩,在开始今天的学习之前,咱们先看一道思考题:
假设苏秦再一次带队抗秦这一天苏秦和4个国家的4位将军赵、魏、韩、楚商量军机要事结果刚商量完没多久苏秦就接到了情报情报上写道联军中可能存在一个叛徒。这时苏秦要如何下发作战指令保证忠将们正确、一致地执行下发的作战指令而不是被叛徒干扰呢
![](https://static001.geekbang.org/resource/image/24/d3/2493047e33459cfa85843dd194ddced3.jpg)
带着这个问题,我们正式进入今天的学习。
首先,咱们先来研究一下,为什么口信消息型拜占庭问题之解很难在实际场景中落地,除了我在开篇提到的非常理论化,没有和实际的需求结合之外,还有其他的原因么?
其实这些问题是后续众多拜占庭容错算法在努力改进和解决的理解了这些问题能帮助你更好地理解后来的拜占庭容错算法包括PBFT算法
## 口信消息型拜占庭问题之解的局限
我想说的是这个算法有个非常致命的缺陷。如果将军数为n、叛将数为 f那么算法需要递归协商 f+1轮消息复杂度为O(n ^ (f + 1))消息数量指数级暴增。你可以想象一下如果叛将数为64消息数已经远远超过**int64**所能表示的了,这是无法想象的,肯定不行啊。
另外尽管对于签名消息不管叛将数比如f是多少经过f + 1轮的协商忠将们都能达成一致的作战指令但是这个算法同样存在“理论化”和“消息数指数级暴增”的痛点。
讲到这儿你肯定明白为什么这个算法很难在实际场景中落地了。可技术是不断发展的算法也是在解决实际场景问题中不断改进的。那么PBFT算法的原理是什么呢为什么它能在实际场景中落地呢
## PBFT是如何达成共识的
我们先来看看如何通过PBFT算法解决苏秦面临的共识问题。先假设苏秦制定的作战指令是进攻而楚是叛徒为了演示方便
![](https://static001.geekbang.org/resource/image/8a/37/8a6fe551e5b99a28e0fed8105ed5cc37.jpg "图1")
需要你注意的是,所有的消息都是签名消息,也就是说,消息发送者的身份和消息内容都是无法伪造和篡改的(比如,楚无法伪造一个假装来自赵的消息)。
首先,苏秦联系赵,向赵发送包含作战指令“进攻”的请求(就像下图的样子)。
![](https://static001.geekbang.org/resource/image/5d/76/5da99fcab9c99b92351e05aca9a9a976.jpg "图2")
当赵接收到苏秦的请求之后会执行三阶段协议Three-phase protocol
* 赵将进入预准备Pre-prepare阶段构造包含作战指令的预准备消息并广播给其他将军魏、韩、楚
![](https://static001.geekbang.org/resource/image/40/2f/40669f5c4bcaffbac446475251f1fa2f.jpg "图3")
那么在这里,我想问你一个问题:魏、韩、楚,收到消息后,能直接执行指令吗?
答案是不能因为他们不能确认自己接收到指令和其他人接收到的指令是相同的。比如赵可能是叛徒赵收到了2个指令分别是“进攻”和“准备30天的粮草”然后他给魏发送的是“进攻”给韩、楚发送的是“准备30天粮草”这样就会出现无法一致行动的情况。那么他们具体怎么办呢我接着说一下。
* 接收到预准备消息之后魏、韩、楚将进入准备Prepare阶段并分别广播包含作战指令的准备消息给其他将军。比如魏广播准备消息给赵、韩、楚如图所示。为了方便演示我们假设叛徒楚想通过不发送消息来干扰共识协商你能看到图中的楚是没有发送消息的
![](https://static001.geekbang.org/resource/image/12/43/12063907d531486261c42691ebc52c43.jpg "图4")
然后当某个将军收到2f个一致的包含作战指令的准备消息后会进入提交Commit阶段这里的2f包括自己其中f为叛徒数在我的演示中是1。在这里我也给你提一个问题这个时候该将军比如魏可以直接执行指令吗
答案还是不能因为魏不能确认赵、韩、楚是否收到了2f 个一致的包含作战指令的准备消息。也就是说,魏这时无法确认赵、韩、楚是否准备好了执行作战指令。那么怎么办呢?别着急,咱们继续往下看。
* 进入提交阶段后,各将军分别广播提交消息给其他将军,也就是告诉其他将军,我已经准备好了,可以执行指令了。
![](https://static001.geekbang.org/resource/image/8a/b4/8a0f34d9098d361f114f91db8c4b1cb4.jpg "图5")
* 最后当某个将军收到2f + 1个验证通过的提交消息后包括自己其中f为叛徒数在我的演示中为1也就是说大部分的将军们已经达成共识这时可以执行作战指令了那么该将军将执行苏秦的作战指令执行完毕后发送执行成功的消息给苏秦。
![](https://static001.geekbang.org/resource/image/c7/4a/c7e8f7152487f65ba14569c50f08254a.jpg "图6")
最后当苏秦收到f+1个相同的响应Reply消息时说明各位将军们已经就作战指令达成了共识并执行了作战指令其中f为叛徒数在我的演示中为1
你看,经过了三轮协商,是不是就指定的作战指令达成了共识,并执行了作战指令了呢?
在这里,苏秦采用的就是**简化版的PBFT算法**。在这个算法中:
* 你可以将赵、魏、韩、楚理解为分布式系统的四个节点其中赵是主节点Primary魏、韩、楚是备份节点Backup
* 将苏秦理解为业务,也就是客户端;
* 将消息理解为网络消息;
* 将作战指令“进攻”,理解成客户端提议的值,也就是希望被各节点达成共识,并提交给状态机的值。
**在这里我想说的是,** PBFT算法是通过签名或消息认证码MAC约束恶意节点的行为也就是说每个节点都可以通过验证消息签名确认消息的发送来源一个节点无法伪造另外一个节点的消息。最终基于大多数原则2f + 1实现共识的。
需要你注意的是最终的共识是否达成客户端是会做判断的如果客户端在指定时间内未收到请求对应的f + 1相同响应就认为集群出故障了共识未达成客户端会重新发送请求。
另外需要你注意的是PBFT算法通过视图变更View Change的方式来处理主节点作恶当发现主节点在作恶时会以“轮流上岗”方式推举新的主节点。
最后我想说的是尽管PBFT算法相比口信消息型拜占庭之解已经有了很大的优化将消息复杂度从O(n ^ (f + 1))降低为O(n ^ 2)能在实际场景中落地并解决实际的共识问题。但PBFT还是需要比较多的消息。比如在13节点集群中f为4
* 请求消息1
* 预准备消息3f = 12
* 准备消息3f \* (3f - f) = 96
* 提交消息:(3f - f + 1) \* (3f + 1)= 117
* 回复消息3f - 1 = 11
也就是说一次共识协商需要237个消息你看消息数还是蛮多的所以我推荐你在中小型分布式系统中使用PBFT算法。
## 内容小结
以上就是本节课的全部内容了本节课我主要带你了解了口信消息型拜占庭问题之解的局限和PBFT的原理我希望你明确这样几个重点。
1. 不管口信消息型拜占庭问题之解,还是签名消息型拜占庭问题之解,都是非常理论化的,未考虑实际场景的需求,而且协商成本非常高,指数级的消息复杂度是很难在实际场景中落地,和解决实际场景问题的。
2. PBFT算法是通过签名或消息认证码MAC约束恶意节点的行为采用三阶段协议基于大多数原则达成共识的。另外与口信消息型拜占庭问题之解以及签名消息型拜占庭问题之解不同的是PBFT算法实现的是一系列值的共识而不是单值的共识。
最后我想说的是相比Raft算法完全不适应有人作恶的场景PBFT算法能容忍(n - 1)/3个恶意节点(也可以是故障节点)。另外相比PoW算法PBFT的优点是不消耗算力所以在日常实践中PBFT比较适用于相对“可信”的场景中比如联盟链。
需要你注意的是PBFT算法与Raft算法类似也存在一个“领导者”就是主节点同样集群的性能也受限于“领导者”。另外O(n ^ 2)的消息复杂度以及随着消息数的增加网络时延对系统运行的影响也会越大这些都限制了运行PBFT算法的分布式系统的规模也决定了PBFT算法适用于中小型分布式系统。
## 课堂思考
当客户端在收到了f + 1个结果就认为共识达成了那么为什么这个值不能小于f + 1呢欢迎在留言区分享你的看法与我一同讨论。
最后,感谢你的阅读,如果这篇文章让你有所收获,也欢迎你将它分享给更多的朋友。