这是 2022 年 5 月整理的拜占庭将军问题笔记。彼时区块链与联盟链正热,PBFT、POW 频繁出现在面试与技术讨论中。笔记从兰伯特的经典论文出发,用”战国六国攻秦”的故事层层深入,整理到口信消息型方案(两轮协商)处截断,本文补全了后续推导与结论。
拜占庭容错(BFT)
拜占庭错误是莱斯利·兰伯特在《拜占庭将军问题》中提出的一个错误模型,描述了一个完全不可信的场景:除了存在故障行为,还存在恶意行为(节点故意发送错误消息、说谎、勾结)。
拜占庭容错(Byzantine Fault Tolerance,BFT):指能容忍拜占庭错误——即使存在恶意节点,系统依然能达成正确共识。
非拜占庭容错(CFT)
非拜占庭容错,又叫故障容错(Crash Fault Tolerance,CFT),解决的是分布式系统中存在故障、但不存在恶意节点的共识问题,比如进程崩溃、服务器硬件故障等。
| 对比项 | CFT(故障容错) | BFT(拜占庭容错) |
|---|---|---|
| 错误模型 | 节点崩溃、网络分区 | 恶意节点,故意作恶 |
| 可信假设 | 节点诚实,只是会挂 | 节点不可信,可能说谎 |
| 典型环境 | 可信环境(企业内网) | 不可信环境(公有链) |
| 常见算法 | 2PC、TCC、Paxos、ZAB、Raft、Gossip、Quorum NWR | POW、PBFT |
核心区别:在可信环境(比如企业内网)中,系统具有故障容错能力就可以了;而在不可信的环境(比如有人作恶)中,系统需要具备拜占庭容错能力。
共识与一致性
很多同学经常误解的一个点:将 Consensus(共识)当成了 Consistency(一致性),把 Paxos、Raft 称为”一致性算法”。
其实 Paxos 和 Raft 是共识算法。之所以出现这个问题,是因为在很多中文文章中,将 Consensus 和 Consistency 都翻译成了”一致性”,其实这样是不合适的——共识和一致性是两个完全不同的概念。
| 概念 | 定义 |
|---|---|
| 共识(Consensus) | 各节点就指定值(Value)达成共识,而且达成共识后的值,就不再改变了 |
| 一致性(Consistency) | 写操作完成后,能否从各节点上读到最新写入的数据:立即能读到 = 强一致性;最终能读到 = 最终一致性 |
一句话:共识解决的是”大家选同一个值”,一致性解决的是”读到的数据是不是最新”。
两忠一叛
战国时期,齐、楚、燕、韩、赵、魏、秦七雄并立,后来秦国的势力不断强大,成了东方六国的共同威胁。于是六国决定联合抗秦。苏秦作为合纵长,挂六国相印,带六国军队叩关函谷,驻军秦国边境。因为各国军队分别驻扎在不同地方,只能通过信使互相联系,苏秦面临一个严峻的问题:如何统一大家的作战计划?
为了便于理解,先假设只有 3 个国家攻打秦国:齐、楚、燕。秦国很强大,只有半数以上的将军参与进攻才能击败敌人。将军们通过信使传递消息,协商一致后在同一时间点发动进攻。
正常情况:少数服从多数
有一天,三位将军讨论明天是进攻还是撤退,按照”少数服从多数”的原则投票表决,两个人意见一致即可:
- 齐根据侦查情况决定撤退;
- 楚和燕根据侦查信息,决定进攻。
那么按照原则,齐也会进攻。最终,3 支军队同时进攻,大败秦军。

叛徒出现:计划不一致
问题来了:一旦有人在暗通秦国,就会出现作战计划不一致的情况。
正常时,齐向楚、燕分别发送”撤退”,燕向齐和楚发送”进攻”。撤退:进攻 = 1:1,无论楚投进攻还是撤退,都会成为 2:1,还是能形成一致方案。
但是,楚这个叛徒在暗中配合秦国,让信使向齐发送”撤退”,向燕发送”进攻”:
- 燕看到的是:撤退:进攻 = 1:2 → 选择进攻;
- 齐看到的是:撤退:进攻 = 2:1 → 选择撤退。
按照”少数服从多数”的原则,就会出现燕单独进攻秦军——最后寡不敌众,被秦军所灭。

这就是”两忠一叛”难题:叛将楚通过发送误导信息,非常轻松地干扰了齐和燕的作战计划,导致这两位忠诚将军被秦军逐一击败。
关键洞察:一个叛徒,就能让两个忠诚将军无法达成共识。诚实的将军们各自”少数服从多数”,但因为各自收到的消息集合不同,各自算出不同的结果——这正是拜占庭问题的本质:每个节点基于不同的局部信息做决策,恶意节点可以放大这种分歧。
解决办法一:口信消息型拜占庭问题之解(OM 算法)
核心思想
- 增加将军数量:三位将军都分拨一部分军队,由苏秦率领,苏秦参与作战计划讨论并执行作战指令。这样,3 位将军的作战讨论就变为 4 位将军的作战讨论,增加了讨论中忠诚将军的数量。
- 预设默认命令:约定如果没有收到命令,就执行预设的默认命令,比如”撤退”。
- 两轮协商:约定进行两轮作战信息协商,而不是一轮。
为什么需要两轮协商
第一轮:每位将军把自己的作战决定(进攻/撤退)发给其他所有将军。
第二轮:每位将军把自己第一轮收到的消息转发给其他所有将军——目的是让每个忠诚将军都掌握”全局视角”。
第一轮:A 把自己的决定发给 B、C、D
第二轮:A 把"我收到 B 说进攻、C 说撤退、D 说进攻"转发给所有人
—— 这样即使有人撒谎,忠诚将军也能交叉验证
没有第二轮会怎样? 一轮通信下,每个将军只信任自己直接收到的消息。叛徒楚可以对齐说”撤退”、对燕说”进攻”、对苏秦说”进攻”,三个忠诚将军收到的是三个不同的消息集,各自投票得出不同结论——共识仍然无法达成。
有了第二轮后:苏秦能告诉齐”楚对我说的是进攻”,齐就能发现楚对我说撤退、对苏秦说进攻,楚在撒谎,从而排除楚的干扰。
理论结论:3m + 1
兰伯特证明了:在口信消息型(Oral Messages)模型中,要容忍 m 个叛徒,将军总数 n 必须满足 n ≥ 3m + 1。
| 叛徒数 m | 最少将军数 n(≥ 3m+1) | 可容忍的最坏比例 |
|---|---|---|
| 1 | 4 | 1/4 |
| 2 | 7 | 2/7 |
| 3 | 10 | 3/10 |
为什么 3 个将军不行? 因为 3 个将军(含 1 个叛徒)时,忠诚将军只有 2 个,无法形成”多数”可信参考——叛徒对两人说的不同,两人无法互相证明谁在撒谎。而 4 个将军(1 叛 3 忠)时,忠诚将军可以通过第二轮交叉验证排除叛徒。
这正是笔记中”把 3 位将军变 4 位”的数学本质:多一个忠诚将军,就多一个交叉验证的参照系。
解决办法二:签名消息型拜占庭问题之解(SM 算法)
口信消息型中,叛徒可以谎称自己收到了某些消息(消息不可验证)。如果引入数字签名,消息不可篡改、不可抵赖,容错能力会显著提升。
签名消息型(Signed Messages):将军的消息带上不可伪造的签名,收到消息的人可以验证”这条消息确实是某将军发的、且未被篡改”。
理论结论:在签名消息模型中,只要 n ≥ 2m + 1 就能容忍 m 个叛徒(允许 n = m + 2 即可达成一致),而且算法复杂度更低、轮数更少。
| 模型 | 最少节点数 | 消息可验证 |
|---|---|---|
| 口信消息(OM) | 3m + 1 | 不可验证,只能靠多数投票 |
| 签名消息(SM) | 2m + 1 | 可验证,签名不可伪造 |
直观理解:口信消息只能靠”人多力量大”来对冲谎言;签名消息直接让谎言无法伪造,所以需要的”冗余节点”更少。
现实中的应用
| 算法 | 类型 | 应用场景 |
|---|---|---|
| POW(工作量证明) | 拜占庭容错 | 比特币等公有链:以算力投票,容忍恶意节点 |
| PBFT(实用拜占庭容错) | 拜占庭容错 | 联盟链(Fabric 等):节点少、效率高 |
| Raft / Paxos / ZAB | 故障容错 | 企业内网:Etcd、Kafka、ZooKeeper 等,假设节点诚实 |
| 2PC / TCC | 故障容错 | 分布式事务,假设节点诚实 |
选型原则:可信内网用 CFT 算法(Raft/Paxos,简单高效);公开不可信环境用 BFT 算法(POW/PBFT,容忍作恶)。
小结
- 拜占庭容错(BFT) 容忍恶意节点;故障容错(CFT) 只容忍节点崩溃。可信环境用 CFT,不可信环境用 BFT。
- 共识(Consensus) 是”各节点就某个值达成一致且不再改变”;一致性(Consistency) 是”能否读到最新数据”。Paxos/Raft 是共识算法,不是一致性算法。
- 两忠一叛:一个叛徒通过发送矛盾消息,就能让两个忠诚将军基于不同局部信息得出不同结论。
- 口信消息之解:增加忠诚将军数量 + 预设默认命令 + 两轮协商(交叉验证);n ≥ 3m + 1。
- 签名消息之解:签名让消息不可伪造;n ≥ 2m + 1。
- 实际应用:POW/PBFT 是 BFT 算法,Raft/Paxos/ZAB 是 CFT 算法。