分布式协议系列文章目录
拜占庭将军问题
一个可靠的计算机系统必须能够应对其中一个或者多个组件发生故障的情况,应对这种类型故障的问题被抽象地表述为拜占庭将军问题,由Leslie Lamport在1982年在一篇《The Byzantine Generals Problem》的论文中提出来的,其可以简单描述为一群拜占庭军队的将军和他们的部队在敌人的城市周围扎营,且这些将军只能通过信使来谈论一致的作战方案。拜占庭军队如果想要战胜敌人,就必须要在行动上保持一致性(进攻或者撤退),但是难免会有叛徒出现来搅局,所以拜占庭将军问题就简化成了一致性和和正确性的问题了。该论文就提出了两种算法来确保忠诚的将军们达成一致的作战方案:其一,口头协议型解决之道;其二,书面协议型解决之道。
口头协议型
结论先行:
- 如果叛徒数少于将军总数量的1/3,拜占庭将军问题就可解。假如叛徒数是m,那么将军总数量至少是3m+1;反之假如将军总数量是n,那么叛徒数量最多是(n-1)/3。
- 叛徒数量决定作战协商的递归轮次,假如叛徒数是m,那么就需要进行m+1轮作战协商才能达成一致性。
概述
口头协议即各个将军之间通过可信的信使来传递信息,且在传递信息的过程中不会告知上一个信息来源是谁,即口头协议满足以下三个条件:
- 每个被发送的消息都能够被正确的投递
- 信息接收者知道消息的来源
- 信息接收者知道缺少的消息
二忠一叛推演
根据结论可知,二忠一叛因为将军总数小于4(3*1+1),所以不可解决的拜占庭将军问题。假如将军分别是A、B和C三位大将,其中C是叛徒。如根据形势:A将军判断发送“进攻”的信息、B将军判断发送“撤退”的信息,以少数服从多数的原则,现在战争的关键就取决于叛徒C发出的信息。
如上图所示,A将军获得{进攻(A将军自己判断)、撤退}、B将军获得{撤退(B将军自己判断)、进攻},根据叛徒C的选择可以有以下四种结局:
- 叛徒C分别向A和B发出进攻,则A将军获得{进攻(A将军自己判断)、撤退、进攻}、B将军获得{撤退(B将军自己判断)、进攻、进攻},根据少数服从多数忠将A和B选择进攻(忠将达成一致);
- 叛徒C分别向A和B发出撤退,则A将军获得{进攻(A将军自己判断)、撤退、撤退}、B将军获得{撤退(B将军自己判断)、进攻、撤退},根据少数服从多数忠将A和B选择撤退(忠将达成一致);
- 叛徒C分别向A发出进攻、向B发出撤退,则A将军获得{进攻(A将军自己判断)、撤退、进攻}、B将军获得{撤退(B将军自己判断)、进攻、撤退},根据少数服从多数忠将A选择进攻、忠将B选择撤退(忠将未能达成一致);
- 叛徒C分别向A发出撤退、向B发出进攻,则A将军获得{进攻(A将军自己判断)、撤退、撤退}、B将军获得{撤退(B将军自己判断)、进攻、进攻},根据少数服从多数忠将A选择撤退、忠将B选择进攻(忠将未能达成一致);
二忠一叛达成一致性
如上述可知二忠一叛是无法使忠将达成一致性,又根据结论可知,如果有一个叛徒,那么将军总数量至少应该是四个(三忠一叛),需要经过两轮作战协商才是达成一致性,推演过程如下:
- 第一轮
引入忠将D,在四位将军中任意选择一位将军作为指挥官依次向其余三位将军发出作战协商,选择A将军作为指挥官向将军B、C和D发出进攻的信息,其中C将军依然是叛徒。
- 第二轮
将军B、C和D分别向另外两个将军发出作战协商,
- 将军B根据自己的观察,依次给将军C和D发送进攻;
- 叛徒C为了扰乱作战,依次给将军B和D发送撤退;
- 将军D根据自己的观察,依次给将军B和C发送进攻;
综上,将军A进攻、将军B获得{进攻、进攻、撤退}、将军C获得{进攻、进攻、进攻}和将军D获得{进攻、进攻、撤退},同样根据少数服从多数的原则将军A、B和D选择进攻,忠将达成一致性。
书面协议型
口头协议型不知道消息完整的来源,就使得其只能运行在叛徒数少于将军总数量1/3的这种情况下。为了解决口头协议型的局限性,那么在书面协议中引入了签名和验签的过程(非对称加密),这样一来如果将军总数是n个,那么无论叛徒有多少个都可以达到一致性,哪怕这个一致性是错误的。假如将军总数是n个,叛徒是m(m < n)个,那么需要m轮次协商作战就可以达成一致性。
二忠二叛推演
因为二忠二叛的场景在口头协议型中无法达成一致性,作为对比这里也以此来进行推演,根据结论所得需要经过三轮次才能达成一致性,假如将军A、B、C和D中,A和B是忠将、C和D是叛徒,推演过程如下:
- 第一轮
- 选择将军A作为指挥官向将军B、叛徒C和叛徒D发送“进攻”的信息(带上将军A的签名);
- 将军B、叛徒C和叛徒D收到将军A发来的信息后,首先进行验签,然后将将军A的信息放入各自的作战协议袋中;
- 此时将军B获得{进攻:A}、叛徒C获得{进攻:A}、叛徒D获得{进攻:A}。
- 第二轮
- 选择将军B({进攻:A})作为指挥官将作战协商袋中的信息,加上自己的签名,依次转发给作战协商袋中没有作战协商信息的将军:此时叛徒C和叛徒D获得{进攻:A:B};
- 选择叛徒C({进攻:A})作为指挥官加上自己的签名,转发给将军B 和叛徒D:此时将军B获得{进攻:A:C}、叛徒D获得{进攻:A:C};
- 叛徒D为了干扰作战协商,使用叛徒C的私钥将信息篡改后,转发给将军B和将军C:此时将军B获得{撤退:A:D}、叛徒C获得{进攻:A:D}。
- 第三轮
- 选择将军B将收到叛徒C和叛徒D的作战协商信息分别转发给叛徒D和叛徒C,此时叛徒C获得{撤退:A:D:B}、叛徒D获得{进攻:A:C:B};
- 选择叛徒C将收到将军B和叛徒D的作战协商信息分别转发给将军B和叛徒D,此时将军B获得{进攻:A:D:C}、叛徒D获得{进攻:A:B:C};
- 选择叛徒D将收到将军B和叛徒C的作战协商信息分别转发给将军B和叛徒C,此时将军B获得{进攻:A:C:D}、叛徒C获得{进攻:A:B:D}。
综上,将军B收到:{进攻:A:D:C;进攻:A:C:D}、叛徒C收到:{撤退:A:D:B;进攻:A:B:D}、叛徒D收到:{进攻:A:C:B;进攻:A:B:C},然后采用既定的choice函数来选择作战协商信息,最后根据此来决定是该进攻还是撤退。
说明
- 通过上述二忠二叛推演过程可得首轮和口头协议一样,选择任意选择一个将军作为指挥官向其余将军发送作战信息;
- 除第一轮被选择的指挥官将军以外,其余将军分别作为指挥官将当轮次收到的作战信息加上自己签名后,转发给其他的将军,直到作战信息附上所有将军的签名为止。