分布式系统–拜占庭将军问题(The Byzantine Generals Problem)

  • 作者: 凯哥Java(公众号:凯哥Java)
  • 分布式事务
  • 时间:2022-11-02 14:11
  • 4341人已阅读
简介 本文档作为分布式系统的学习基础,通过拜占庭问题取尝试先了解分布式系统里面可能存在的基础问题。拜占庭将军问题,主要为了解决在已知有成员不可靠的情况下,其余忠诚的将军需要在不受叛徒或间谍的影响下达成一致的协议的问题。拜占庭错误是莱斯利·兰伯特在《拜占庭将军问题》中提出的一个错误模型,描述了一个完全不可信的场景,除了存在故障行为,还存在恶意行为。顾名思义,拜占庭容错(ByzantineFaultTole

🔔🔔🔔好消息!好消息!🔔🔔🔔

有需要的朋友👉:联系凯哥 微信号 kaigejava2022

本文档作为分布式系统的学习基础,通过拜占庭问题取尝试先了解分布式系统里面可能存在的基础问题。拜占庭将军问题,主要为了解决在已知有成员不可靠的情况下,其余忠诚的将军需要在不受叛徒或间谍的影响下达成一致的协议的问题。

拜占庭错误是莱斯利·兰伯特在《拜占庭将军问题》中提出的一个错误模型,描述了一个完全不可信的场景,除了存在故障行为,还存在恶意行为。顾名思义,拜占庭容错(Byzantine Fault Tolerance,BFT),就是指能容忍拜占庭错误了。莱斯利·兰伯特( Leslie Lamport )通过这个比喻,表达了计算机网络中所存在的一致性问题。

非拜占庭容错,又叫故障容错(Crash Fault Tolerance,CFT),解决的是分布式系统中存在故障,但不存在恶意节点的共识问题,比如进程奔溃,服务器硬件故障等等。


共识问题

共识难题,也就是如何在可能有误导信息的情况下,采用合适的通讯机制,让多个将军达成共识,制定一致性的作战计划?

李牧作为合纵长,燕,齐,韩三国合纵想要攻打秦国,但是只有3国出半数以上的将军才能打赢秦国。

共识在这里表示的就是三国将军都收到准确的消息,达成一致性决定在某个时间攻打。一般情况下,每国投票,最后少数服从多数即可达成一致决定,例如:

燕,齐将军决定要打秦国

韩国将军想撤退

按照少数服从多数的原则,韩国将军也会出兵攻打秦国。

两忠一叛

可以看到,在正常情况下,信息一致,是能够形成共识的。但是,只要一旦燕,齐国将军有其中一国通秦,就会形成作战计划不一致的问题。例如燕国将军通秦,燕国将军向韩国将军发攻打,给齐国将军发送撤退:

韩国将军收到的信息就是,2攻打:1撤退

齐国将军看到的信息就是,1攻打:2撤退

按照少数服从多数的原则,最终就是韩国将军自己面对秦国,最后败于秦国。这里的问题就出在,其中里面有一国出了叛徒,导致发送了错误的信息。

解决方案

口信消息型拜占庭问题之解

首先,3位将军都分拨一部分军队,由李牧率领,李牧参与作战计划讨论并执行作战指令。这样,3 位将军的作战讨论,就变为了 4位将军的作战讨论,这能够增加讨论中忠诚国家的数量。

然后,4 位将军还约定了,如果没有收到命令,就执行预设的默认命令,比如“撤退”。除此之外,还约定一些流程来发送作战信息、执行作战指令

第一轮

先发送作战信息的将军作为指挥官,其他的将军作为副官;指挥官将他的作战信息发送给每位副官;每位副官,将从指挥官处收到的作战信息,作为他的作战指令;如果没有收到作战信息,将把默认的“撤退”作为作战指令。

第二轮

除了第一轮的指挥官外,剩余的 3 位将军将分别作为指挥官,向另外 2 位将军发送作战信息;然后,这 3 位将军按照“少数服从多数”,执行收到的作战指令。

忠诚将军先发起命令

假设先由李牧向3个将军发起进攻的命令,在第一轮作战信息协商中,李牧向燕、齐、韩发送作战指令“进攻”。

f6fcc19d3c84c9903bdd0d968ce9e1cf.png

在第二轮作战信息协商中,燕、齐、韩分别作为指挥官,向另外 2 位发送作战信息“进攻”,因为韩国将军已经通敌了,所以,为了干扰作战计划,韩国将军发送“撤退”作战指令。

39dfd083ec06a15b04551b4fa0189e2c.png

最终,齐和燕收到的作战信息都是“进攻、进攻、撤退”,按照原则,齐和燕与李牧一起执行作战指令“进攻”,实现了作战计划的一致性,保证了作战的胜利。


通敌国先发送作战命令

假设先由韩过将军向3个将军发起进攻的命令,在第一轮作战信息协商中,韩国将军向李牧、燕发送作战指令“撤退”,向齐国将军发送作战指令"进攻"。

64d677030e1a104e834e3c2e1e0d99ec.png

然后,在第二轮作战信息协商中,李牧、赵、燕分别作为指挥官,向另外两位发送作战信息。

59cbd28388549eedcf145f63ebaa1c0e.png

最终,李牧、燕和齐收到的作战信息都是“撤退、撤退、进攻”,按照原则,李牧、齐和燕一起执行作战指令“撤退”,实现了作战计划的一致性。也就是说,无论叛将楚如何捣乱,李牧、齐和燕,都执行一致的作战计划,保证作战的胜利。

这个解决办法,其实是兰伯特在论文《The Byzantine Generals Problem》中提到的口信消息型拜占庭问题之解:如果叛将人数为 m,将军人数不能少于 3m + 1 ,那么拜占庭将军问题就能解决了。

这个算法有个前提,也就是叛将人数 m,或者说能容忍的叛将数 m,是已知的。在这个算法中,叛将数 m 决定递归循环的次数(也就是说,叛将数 m 决定将军们要进行多少轮作战信息协商),即 m+1 轮(所以,你看,只有魏是叛变的,那么就进行了两轮)。你也可以从另外一个角度理解:n 位将军,最多能容忍 (n - 1) / 3 位叛将。

不过,这个算法虽然能解决拜占庭将军问题,但它有一个限制:如果叛将人数为 m,那么将军总人数必须不小于 3m + 1。

不过,这个算法虽然能解决拜占庭将军问题,但它有一个限制:如果叛将人数为 m,那么将军总人数必须不小于 3m + 1

签名消息型拜占庭问题之解

口信消息型需要增加更多的忠诚的将军,签名消息型就是允许在不添加新的将军的前提下,达到最后的一致性。

签名就好比李牧的印章、虎符等信物,并且必须满足以下条件:

忠诚将军的签名无法伪造,而且对他签名消息的内容进行任何更改都会被发现;

任何人都能验证将军签名的真伪。

忠诚将军先发起命令

如果忠诚的将军,比如韩国先发起作战信息协商,一旦齐国修改或伪造收到的作战信息,那么燕国在接收到齐国的作战信息的时候,会发现韩国的作战信息被修改,齐国已叛变,这时他将忽略来自齐国的作战信息,最终执行韩国发送的作战信息。

63bea742a808e7f977d2eea7adb6127f.png

通敌国先发起命令

如果叛变将军齐先发送误导的作战信息,那么,韩和燕将按照一定规则(比如取中间的指令)在排序后的所有已接收到的指令中(比如撤退、进攻)中选取一个指令,进行执行,最终执行一致的作战计划。

cc0dcaaa4755c3108204ceac553c293e.png

签名消息的拜占庭问题之解,也是需要进行 m+1 轮(其中 m 为叛将数)。你也可以从另外一个角度理解:n 位将军,能容忍 (n - 2) 位叛将只有一位忠将没有意义,因为此时不需要达成共识了)。可以尝试去思考在2忠2叛的情况,在这里不做过多的赘述。可能你会觉得怎么可能事先知道叛军的,m根本就不知道。记住,轮次是通过n-1次定义的,而不是通过确定的叛军数确定的。

真实分布式场景

各位将军,为计算机节点;
忠诚的将军,为正常运行的计算机节点;
叛变的将军,为出现故障并会发送误导信息的计算机节点;
信使被杀,为通讯故障、信息丢失;
信使被间谍替换,为通讯被中间人攻击,攻击者在恶意伪造信息和劫持通讯。

拜占庭将军问题描述的是最困难的,也是最复杂的一种分布式故障场景,除了存在故障行为,还存在恶意行为的一个场景。你要注意,在存在恶意节点行为的场景中(比如在数字货币的区块链技术中),必须使用拜占庭容错算法(Byzantine Fault Tolerance,BFT)。除了故事中提到两种算法,常用的拜占庭容错算法还有:PBFT 算法,PoW 算法。


计算机分布式系统中,最常用的是非拜占庭容错算法,即故障容错算法(Crash Fault Tolerance,CFT)。CFT 解决的是分布式的系统中存在故障,但不存在恶意节点的场景下的共识问题。 也就是说,这个场景可能会丢失消息,或者有消息重复,但不存在错误消息,或者伪造消息的情况。常见的算法有 Paxos 算法、Raft 算法、ZAB 协议。


如果能确定该环境中各节点是可信赖的,不存在篡改消息或者伪造消息等恶意行为(例如 DevOps 环境中的分布式路由寻址系统),推荐使用非拜占庭容错算法;反之,推荐使用拜占庭容错算法,例如在区块链中使用 PoW 算法。


借拜占庭将军的故事来描述分布式共识的问题,并且探讨和论证了一些解决方法。拜占庭将军是分布式领域最复杂的一个容错模型,但是理解了这个问题之后,就能掌握分布式共识问题的解决思路,还能更深刻地理解常用的共识算法。


TopTop