拜占庭将军问题
拜占庭将军问题是一个共识问题: 首先由Leslie Lamport与另外两人在1982年提出,被称为The Byzantine Generals Problem或者Byzantine Failure。核心描述是军中可能有叛徒,却要保证进攻一致,由此引申到计算领域,发展成了一种容错理论。随着比特币的出现和兴起,这个著名问题又重入大众视野。
关于拜占庭将军问题,一个简易的非正式描述如下:
拜占庭帝国想要进攻一个强大的敌人,为此派出了10支军队去包围这个敌人。这个敌人虽不比拜占庭帝国,但也足以抵御5支常规拜占庭军队的同时袭击。基于一些原因,这10支军队不能集合在一起单点突破,必须在分开的包围状态下同时攻击。他们任一支军队单独进攻都毫无胜算,除非有至少6支军队同时袭击才能攻下敌国。他们分散在敌国的四周,依靠通信兵相互通信来协商进攻意向及进攻时间。困扰这些将军的问题是,他们不确定他们中是否有叛徒,叛徒可能擅自变更进攻意向或者进攻时间。在这种状态下,拜占庭将军们能否找到一种分布式的协议来让他们能够远程协商,从而赢取战斗?这就是著名的拜占庭将军问题。
应该明确的是,拜占庭将军问题中并不去考虑通信兵是否会被截获或无法传达信息等问题,即消息传递的信道绝无问题。Lamport已经证明了在消息可能丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的。所以,在研究拜占庭将军问题的时候,我们已经假定了信道是没有问题的,并在这个前提下,去做一致性和容错性相关研究。
拜占庭容错算法
我们已经了解了拜占庭将军问题的场景,并且明确了这个问题的解决是建立在通信兵可以正确的传达信息的基础上的,即信道绝对可信。接下来,我们将探讨拜占庭将军问题的实质。
拜占庭容错算法是解决在同步网络,任意失败模型情况下达成一致性共识。
拜占庭将军问题实质
回顾问题,一群将军想要实现某一个目标(一致进攻或者一致撤退),但是单独行动行不通,必须合作,达成共识;由于叛徒的存在,将军们不知道应该如何达到一致。注意,这里“一致性”才是拜占庭将军问题探讨的内容,如果本来叛徒数量就已经多到了问题不可解的地步,这个就是“反叛”的问题了;同时,我们的目标是忠诚的将军能够达成一致,对于这些忠诚的将军来说,进攻或者撤退都是可以的,只要他们能够达成一致就行。
但是,光靠“一致”就可以解决问题吗?考虑一下,如果万事俱备,客观上每个忠诚的将军只要进攻了就一定能够胜利,但是却因为叛徒的存在他们都“一致的”没有进攻;反之,条件不利,将军们不应该进攻,但是却因为叛徒的存在所有人都“一致的”进攻了。
可以发现,只有“一致性”是不足以解决拜占庭将军问题的,我们还需要提出一个“正确性”要求。这个要求是值得斟酌的,因为如果客观来看或许会有“绝对正确的”判断,但是针对每一个将军,大家的判断或许都不相同,我们如何定义“正确”呢?我们或许可以简单地说,正确就是每个忠诚的将军都正确的表达了自己的意思,不会因为叛徒让别的将军认为忠诚的将军是叛徒而不采用他传达的消息。
至此,我们将拜占庭将军问题简化成了,所有忠诚的将军都能够让别的将军接收到自己的真实意图,并最终一致行动;而形式化的要求就是,“一致性”与“正确性”。
如果将问题推广开来,可以发现针对一致性和正确性的算法并不要求命令必须是“进攻/撤退”或是“1/0”,而可以是“发送消息1/发送消息2/待机”或“x/y/z/w”,这意味着拜占庭将军问题算法可以为多种分布式系统提供启发,比如电力系统或网络系统。
由此可见,这个问题说到底是一个关于一致性和正确性的算法问题,这个算法是针对的是忠诚的将军,因为叛徒可以做出任何超出约定的判断。我们就是要在有叛徒的干扰下,找到一个抗干扰的算法。要解决这个算法问题,我们需要将形式化要求具体化。
口头协议推演
下面的这个截图是从Lamport发表的论文中截取的:
对于这个算法需要说明的是:
在第一轮 将军会把消息发送给所有的副官,第i个副官收到的记为 Vi。如 1(这里代表的是Attack)
在第二轮里面,Li(即第i个副官)会怀疑将军发来的消息Vi是对还是错,于是他会问其余的副官。这样他就会得到剩下的(n-2)个副官的值。 i从1到n-1,所以每个副官都会得到剩余的n-2个副官手里的Vi。在这一步骤里,忠诚的副官j会直接将自己的 Vj发送给其它人。叛徒则会发假消息。
在n=7,m=2的时候如果将军是忠臣的话,那么在第二轮忠诚的副官确实已经可以判断出要做的决定,因为他们会收到(1 1 1 0 0 )再加上将军发来的1就是 1 1 1 1 0 0 但是这个算法是递归的所有必须要到第三轮。并且如果将军是个叛徒的话,那么第二轮有情形是做不出决定的。
这里对进入第三轮的解释是,如L1收到其它L2~L6发来的Vj, 但是他要怀疑准确性,比如L1会想L2发给自己是否是正确的呢?那么就进入第三轮进行投票。
(3)在第三轮里面,接着(2)中后面的问题。L1会依次询问L3,4,5,6,问他们上一轮L2给他们发了什么,然后L1会得到在(2)中 L2->L3, L2->L4,L2->L5, L2->L6的值 这样再结合自己的L2->L1的值,从这5个里面用majority函数投出决定得到L2发给自己的消息值。依次再进行L3,L4,L5,L6在第二轮中发给自己的消息的确认。
这样L1就完成了第二轮的确认。之后L1再从第一步中将军发给自己的vi和第二轮中确定的5个值中投出自己的决定。
其余的L2,L3.等等也进行同样的步骤。
如果还是没清晰的话,直接看下面的过程:
这里需要注意的是:Lamport提出的容错的两个条件
IC1:即所有的忠诚的副官要遵守同一个命令,即达成一致;
IC2:假如将军是忠诚的,那么每一个忠诚的副官都应该按照将军的意思行事。
这里将军是叛徒,所以只要满足IC1条件即可。
Step1 : C给L1~L6 依次发 A R A R AX (A,R代表攻击和撤退,这里因为C是叛徒,所以可以随便发给L1-L5消息,这里只是一个例子,可以用其他的值,只要最后满足IC1就可以)
Step2: L1为例,L1会怀疑将军发给自己的消息,于是会问L2-L6
L2(R) | L3(A) | L4(R) | L5(A) | L6(X) | A(L1) | |
---|---|---|---|---|---|---|
L2 | R | R | R | R | A | R |
L3 | A | A | A | A | R | A |
L4 | R | R | R | R | A | R |
L5 | A | A | A | A | R | A |
L6 | R | A | R | A | A | A |
算法步骤:
确认将军 | L2(R) | L3(A) | L4(R) | L5(A) | L6(X) | A(L1) |
---|---|---|---|---|---|---|
确认L2 | R | R | R | R | A | R |
确认L3 | A | A | A | A | R | A |
确认L4 | R | R | R | R | A | R |
确认L5 | A | A | A | A | R | A |
确认L6 | R | A | R | A | A | A |
Step3:其实在第三步中 L1会依次确认在step2中, L2~L6发给自己的信息. 例如确认L2时 会问L3-L6,在Step2中L2发给你们了什么
最后得到 R, R, R,X (因为L6这时候肯定又说谎)再结合自己的R , L1确定在Step2中收到L2发来的是R,之后又确认了L3-L6。大家可以自己在草稿纸上画出。
其实因为L1-L5都是忠诚,他们不会在Step2中撒谎,所以只需投票L6即可,(A R A R A )是L6发给L1-L5的信息,最后投出发的是A ,将A修改到step2中L1-L5收到的
信息中其余的信息可以不用改。
最后得到L1-L5均是 4个A, 2个R。以L1为例=R A R A A A
即L1~L5达成了一致。