分布式基础- 拜占庭将军问题

一 背景

拜占庭将军问题是如何通过通讯方式来达成共识得问题,Leslie Lamport 来借助这个问题说明如何在分布式环境下达成共识。

拜占庭将军问题是这样的:拜占庭帝国的军队在围攻一座城市,拜占庭帝国的军队分成许多小队,围着城市的不同方向,每支小队都有一个将军领导。这些将军们通过信使来传递消息。每位将军观察敌人的情况后,会给出一个各自的行动建议,但最终所有小队都要达成一致的目标,或者进攻或者撤退,不然会因为敌军强大而被逐个击败。

这里面的问题是,这些小队的将军有可能叛变了,或者小队派出的信使中途被敌军截杀,消息被更换,当然这其中的有问题的消息比较少。

类比在分布式计算机中,所有小队要达成的目标,类似分布式计算机协商,对某个操作达成共识的过程。其中信使中途被敌军截杀 ,篡改和将军叛变,对应分布式网络中消息的丢失,发生错误,或中间人攻击。

二 解决办法

在聊解决办法之前,我们来先看看有问题的场景:

2.1 二忠一叛

假设拜占庭帝国有三支军队,围攻城市,这三支军队的将军为 A,B,C 表示,三支军队按照少数服从多数的原则进行决策,假设消息传递如下图: 消息交互过程

如上图,假设是 C 叛变,对 AB 两个将军发送不同的消息,对他们决策进行误导: A 收到的消息有:

  1. 自己的进攻消息。
  2. B 的撤退消息。
  3. C 的撤退消息 按照少数服从多数的原则,执行进攻。 B 收到的消息有:
  4. 自己的撤退消息。
  5. A 的进攻消息。
  6. C 的进攻消息。 按照少数服从多数的原则,执行进攻。 这样 A 决策和 B 的决策相反,从而导致攻城失败。

2.2 口信消息型解决方法

论文中提到了两个解决办法,其中一个就是通过增加将军和一些规则来的,称为 我们可以这里面增加了一位将军,并且做了规定,如果没有收到命令就执行默认的动作,比如撤退,并且执行二次协商。在我看来,通过增加将军人数,通过更多的交互次数,让信息更充分交互,减少信息熵。说明下,这个算法只能解决最终决策一致的问题,并没有一定会做出正确的选择,因为和开始发起作战消息的将军有很大的关系。

第一次协商

  • 先发送作战信息的将军是指挥官,其他将军作为副官。
  • 指挥官将他的作战信息发送给每位副官。
  • 每位副官收到命令后,作为他的作战命令,如果没有收到作战命令,那么将撤退作为作战命令。

第二次协商

  • 除指挥官外,其他三个副官中,选择一位作为指挥官,向其他两位发送作战命令
  • 然后这几位将军按照少数服从多数的原则来执行作战命令。

举例如下: 第一轮 说明:第一轮 A 作为指挥官发送进攻命令给 B,C,D 三个副官。

第二轮
第二轮

说明: 第二轮中,B 和 D 按照第一轮收到的命令发送进攻消息给另外两个将军,C 由于叛变,所以故意发送撤退消息给 B 和 D。 这时候: B 收到 两个进攻消息,一个撤退消息,执行进攻。 D 收到 两个进攻消息,一个撤退消息,也执行进攻。 A 就是自己的进攻消息,所以也执行进攻。 这样最终执行的命令就在忠实将军中达成了一致。

如果 C 作为指挥官叛变了,那会不会达成一致那? 第一轮 说明:作为叛变者,肯定会捣乱,就给 BD 发送进攻命令,给 A 发送撤退命令。

第二轮
第二轮

说明:进行第二轮信息交互后: A: 收到一个撤退命令,2 个进攻命令,决定进攻。 B: 收到两个进攻命令,一个撤退命令,决定进攻。 D: 收到两个进攻命令,一个撤退命令,决定进攻。 所以三个忠实将军也达成了一致。

注意:

  1. 最终的作战命令和第一个指挥官的命令有很大关系,如果指挥官没叛变则按照他的命令执行结果。
  2. 如果指挥官叛变了,则按照他发的最多的消息为准。
  3. 消息会最终达成一致。

这个口信消息解决拜占庭将军问题还有以下约束:

  1. 每个消息都被正确发送。
  2. 消息接收者知道谁发送消息。
  3. 消息缺席可以检测。
  4. 如果叛将人数为 m,总的将军人数不能少于 3m+1,叛将数 m 决定了循环的次数,m 个叛将需要经过 m 轮递归循环的作战信息协商,即一共需要 m+1 轮.

这个是兰伯特在论文中的解决方法,这里面的叛将数目在总人数中是有要求的,n 位将军只能有(n-1)/3 位叛将.

2.3 签名消息类型

兰伯特的论文中还提出一种签名的方式在不增加将军的前提下,解决二忠一叛的问题,签名要实现以下几个特性:

  1. 忠诚将军的签名无法伪造,对消息的任何更改都会被发现.
  2. 任何人都可以验证将军的签名真伪.

这两个特性就是数字签名的特性,在 HTTPS 协议中也常用。主要实现两个目的: 1) 消息无非篡改,这个通过对消息做摘要来保障一旦有修改,摘要就发生变化就发现了。 2) 消息的防伪, 就是防止别人伪造,让我们可以对消息的发送者做认证。

实现是通过非对称加密算法来实现的,我们发送消息时候,可以先对消息做个摘要,然后我们用自己的私钥对摘要进行加密,加密后的信息和原始报文一起发送给对方。对方收到消息后,先拆出加密的摘要,在用我们的公钥(公钥是公开的)进行解密,得到一个摘要值,假设为摘要 1,再通过同样的摘要算法计算我们的消息,得到一个摘要值 2,将两个摘要值进行比较,相同即说明是我们发送的消息,达到防伪的目的,如下图:

签名和签名校验过程
签名和签名校验过程

处理方法也是类似前面消息类型, 如果三位将军一名叛将需要进行两轮, 我们先假设是忠诚将军作为指挥官发送信息;然后再举个叛将作为指挥官的例子来看最终忠诚将军们是否可以达成一致作战命令.

1) 先发送消息的将军为指挥官,其他为副官。 2) 第一轮:指挥官发送消息(加了签名的)给副官,副官如果收到多条指挥官发送的消息,将这些消息按照一定顺序排队。 3)第二轮 剩下的副官分别作为指挥官,发送收到的作战信息(带自己签名) 转给其他副官。 4) 最后每个将军按照找到的作战信息排队,取第一个或中间一个或最后一个作为自己的作战命令。

叛将第一轮作为副将 签名消息第一轮 c为叛将

进入第二轮后,叛将 C 有几种决策: 1) 装死不发作战消息给 D,则 D 只有收到 A 的进攻命令,和 A 一样都有一个进攻命令,执行进攻没问题。 2) C 发送作战信息给 D,主要把 A 发送的进攻,伪造成了撤退,那么由于 C 没有 A 的签名,所以这种伪造会被 D 发现,D 忽略判断信息,则还剩下一个进攻命令,还继续执行进攻,如下图: 签名消息第二轮 C伪造消息 3) 如果 C 不伪造,继续发送进攻消息给 D,那么 D 和 A 也会进攻。

如果叛将第一轮作为指挥官会不一样嘛?
叛将第一轮作为指挥官

1) 第一轮:C 作为指挥官发起作战命令 c 既然是叛将,要捣乱,那么发送给 A 和 D 的作战信息不一样,如果一样就不需要讨论了: 签名消息第一轮

2) 第二轮:第一轮的副将轮流作为指挥官交换信息 签名消息第二轮

3) D 和 A 将收到的指挥官信息后,按照指定的顺序排序,按照约定的顺序,比如都取最后一个命令作为作战命令,这样 A 和 D 都执行一致的作战命令。

由于采用签名消息的办法,所以可以支持更多叛将的情况,支持 n-2 个叛将,当然在 n 为 3 的时候没多大意义,因为只有一个忠实将军,无论是进攻还是撤退都满足要求;另外执行信息交互的次数和消息型一样,如果 m 个叛将,则需要执行 m+1 轮。

三 总结

拜占庭将军问题的解决算法,不在乎结果,在乎的是忠诚将军会不会达成一致,会不会去当炮灰。 只是解法比较朴素,现实中落地的话还有些问题,现在用的多的多是改进型拜占庭将军问题解决算法。

在分布式网络中,拜占庭将军中遇到的是叛将问题一般比较少,除非像比特币那种公开的网络,这种场合存在恶意用户就需要用拜占庭容错算法(Byzantine Fault Tolerance,BFT)来达成共识,常见的拜占庭容错算法有: PBFT 和 Pow 算法。

如果是内部的局域网中的分布式系统,可以采用非拜占庭容错算法,即解决消息丢失,重复,但是不解决消息错误的情况,常见的算法有:Paxos,Raft,ZAB 算法。

四 古诗欣赏

             《桃花庵歌》
                             -- 唐寅
桃花坞里桃花庵,桃花庵里桃花仙;
桃花仙人种桃树,又摘桃花卖酒钱。
酒醒只在花前坐,酒醉还须花下眠;
半醒半醉日复日,花落花开年复年。
但愿老死花酒间,不愿鞠躬车马前;
车尘马足富者趣,酒盏花枝贫者缘。
若将富贵比贫贱,一在平地一在天;
若将贫贱比车马,他得驱驰我得闲。
别人笑我太疯癫,我笑他人看不穿;
不见五陵豪杰墓,无花无酒锄作田。
原文地址:https://www.cnblogs.com/seaspring/p/13822122.html