| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,251 @@ | |||
| 1 | + # 用三国杀讲分布式算法,舒适了吧? | ||
| 2 | + | ||
| 3 | + ## 前言 | ||
| 4 | + | ||
| 5 | + > 《三国杀》是一款热门的卡牌游戏,结合中国三国时期背景,以身份为线索,以卡牌为形式,益智休闲,老少皆宜。 | ||
| 6 | + | ||
| 7 | + 东汉末年,袁绍作为盟主,汇合了十八路诸侯一起攻打董卓。 | ||
| 8 | + | ||
| 9 | + 在讲解之前,我们先聊下分布式协议和算法整体脉络。 | ||
| 10 | + | ||
| 11 | + 现在很多开发同学对分布式的组件怎么使用都有一定经验,也知道 `CAP` 理论和 `BASE` 理论的大致含义。但认真去看分布式算法的真的很少,原因有三: | ||
| 12 | + | ||
| 13 | + - 担心算法过于复杂,所以花的时间很少。 | ||
| 14 | + - 网上的资料能用大白话将分布式算法讲清楚的比较少。 | ||
| 15 | + - 学习分布式算法没有一条清晰的路线。 | ||
| 16 | + | ||
| 17 | + 我会在后续的文章中用`故事、大白话`的方式来讲解分布式算法的原理,以及学习路线到底是怎么样的? | ||
| 18 | + | ||
| 19 | + ## 学习路线 | ||
| 20 | + | ||
| 21 | + 学习分布式协议和算法的路线可以是先学习四大基础理论,作为地基,再学习分布式协议和算法,就像是在地基上建房子。地基打好了,才能建更稳固的高楼大厦。 | ||
| 22 | + | ||
| 23 | + ### 四大基础理论 | ||
| 24 | + | ||
| 25 | + - 拜占庭将军问题 | ||
| 26 | + - CAP 理论 | ||
| 27 | + - ACID 理论 | ||
| 28 | + - BASE 理论 | ||
| 29 | + | ||
| 30 | + ### 八大分布式协议和算法 | ||
| 31 | + | ||
| 32 | + - Paxos 算法 | ||
| 33 | + - Raft 算法 | ||
| 34 | + - 一致性 Hash 算法 | ||
| 35 | + - Gossip 协议算法 | ||
| 36 | + - Quorum NWR 算法 | ||
| 37 | + - FBFT 算法 | ||
| 38 | + - POW 算法 | ||
| 39 | + - ZAB 协议 | ||
| 40 | + | ||
| 41 | + 因篇幅原因,本篇只涉及拜占庭将军问题。 | ||
| 42 | + | ||
| 43 | + ## 拜占庭将军问题 | ||
| 44 | + | ||
| 45 | + 大家可能听过拜占庭将军问题。它是由莱斯利·兰伯特提出的点对点通信中的基本问题, | ||
| 46 | + | ||
| 47 | + `拜占庭`位于如今的土耳其的`伊斯坦布尔`,是`东罗马帝国`的首都。由于当时拜占庭罗马帝国国土辽阔,为了达到防御目的,每个军队都分隔很远,将军与将军之间只能靠信差传消息。在战争的时候,拜占庭军队内所有将军和副官必须达成一致的共识,决定是否有赢的机会才去攻打敌人的阵营。但是,在军队内有可能存有叛徒和敌军的间谍,这个就是拜占庭容错问题。 | ||
| 48 | + | ||
| 49 | + 实际上拜占庭问题是分布式领域最复杂的一个容错模型,一旦理解它,就能掌握分布式共识问题的解决思路,还能帮助大家理解常用的共识算法,也可以帮助我们在工作中选择合适的算法,或者设计合适的算法。 | ||
| 50 | + | ||
| 51 | + 为什么第一个基础理论是拜占庭将军问题? | ||
| 52 | + | ||
| 53 | + **因为它很好地抽象出了分布式系统面临的共识问题。** 上面提到的 8 种分布式算法中有 5 种跟拜占庭问题相关,可以说弄懂拜占庭问题对后面学习其他算法就会容易很多。 | ||
| 54 | + | ||
| 55 | + 下面我用三国杀游戏中的身份牌来讲解拜占庭将军问题。 | ||
| 56 | + | ||
| 57 | + ## 三国杀身份牌 | ||
| 58 | + | ||
| 59 | + 三国杀中主要有四种身份:主公、忠臣、反贼、内奸。每个游戏玩家都会获得一个身份牌。主公只有 1 个。忠臣 最多 2 个,反贼最多 4个,内奸最多一个。 | ||
| 60 | + | ||
| 61 | + ### 主公 | ||
| 62 | + | ||
| 63 | +  | ||
| 64 | + | ||
| 65 | + **获胜条件:** 消灭所有反贼和内奸 | ||
| 66 | + | ||
| 67 | + **技巧:** 以自己生存为首要目标,分散反贼注意力。配合忠内剿灭反贼并判断谁是忠谁是内。 | ||
| 68 | + | ||
| 69 | + ### 忠臣 | ||
| 70 | + | ||
| 71 | +  | ||
| 72 | + | ||
| 73 | + **获胜条件:** 保护主公存活的前提下消灭所有反贼和内奸。 | ||
| 74 | + | ||
| 75 | + **技巧:** 忠臣是主公的屏障,威慑反贼和内奸的天平。 | ||
| 76 | + | ||
| 77 | + ### 反贼 | ||
| 78 | + | ||
| 79 | +  | ||
| 80 | + | ||
| 81 | + **获胜条件:** 消灭主公即可获胜。 | ||
| 82 | + | ||
| 83 | + **技巧:** 反贼作为数量最多的身份,需要集中火力猛攻敌人弱点。正确的思路是获胜的关键。 | ||
| 84 | + | ||
| 85 | + ### 内奸 | ||
| 86 | + | ||
| 87 | +  | ||
| 88 | + | ||
| 89 | + **获胜条件:** 先消灭反贼和忠臣,最后与主公单挑成为最后唯一生还者。 | ||
| 90 | + | ||
| 91 | + **技巧:** 正确的战术+ 冷静的头脑+ 运气。 | ||
| 92 | + | ||
| 93 | + ## 还原拜占庭问题 | ||
| 94 | + | ||
| 95 | + 东汉末年,袁绍作为盟主,汇合了十八路诸侯一起攻打董卓。把董卓定为反贼,袁绍定为主公,另外有两个忠诚和一个内奸,就选这三个风云人物:曹操,刘备,孙坚(孙权的爸比),内奸扮演的角色是忠臣,主公和两个忠臣不知道内奸的身份,都当作忠臣对待了。 | ||
| 96 | + | ||
| 97 | + ![战局 3 vs 2] | ||
| 98 | + | ||
| 99 | + 董卓是非常强大的,拥有精良的西凉兵,麾下还有战神吕布。大家都知道三英站吕布的故事,吕布以一已之力对阵刘备、张飞、关羽三人。 | ||
| 100 | + | ||
| 101 | + 要想干掉董卓,袁绍必须统一忠臣的作战计划,三位忠臣还不知道有什么其他花花肠子,有一个还是内奸。如果内奸暗通反贼董卓,给忠臣发送误导性的作战信息,该怎么办?另外假定这几个忠臣都是通过书信交流作战信息,如果书信被拦截了或书信里面的信息被替换了咋办?这些场景都可能扰乱作战计划,最后出现有的忠臣在进攻,有的忠臣撤退了。那么反贼就可以乘此机会发起进攻,逐一攻破。 | ||
| 102 | + | ||
| 103 | + 袁绍本来就没有曹操的机智,那他**如何让忠臣们达成共识,制定统一的作战计划呢?** | ||
| 104 | + | ||
| 105 | + 上面的映射关系就是一个拜占庭将军问题的一个简化表述,袁绍现在面临的就是典型的**共识问题**。也就是在可能有误导信息的情况下,采用合适的通讯机制,让多个将军**达成共识**,制定一致性的作战计划。 | ||
| 106 | + | ||
| 107 | + ## 一方选择撤退 | ||
| 108 | + | ||
| 109 | + 刘备、曹操、孙坚通过**信使**传递进攻或撤退的信息,然后进行协商,到底是进攻还是撤退。遵循少数服从多数,不允许弃权。 | ||
| 110 | + | ||
| 111 | + 曹操疑心比较重,侦查了反贼的地形后,决定撤退。而刘备和孙坚决定进攻。 | ||
| 112 | + | ||
| 113 | + - 刘备决定**进攻**,通过信使告诉曹操和孙坚**进攻**。 | ||
| 114 | + | ||
| 115 | + - 曹操决定**撤退**,通过信使告诉刘备和孙坚**撤退**。 | ||
| 116 | + | ||
| 117 | + - 孙坚决定**进攻**,通过信使告诉曹操和刘备**进攻**。 | ||
| 118 | + | ||
| 119 | +  | ||
| 120 | + | ||
| 121 | + 曹操收到的信息:进攻 2 票,自己的一张撤退票,票数一比,进攻票:撤退票 = 2 : 1,按照上面的少数服从多数原则进行投票表决,曹操还是会进攻。那么三方的作战方案都是进攻,所以是一个**一致性**的作战方案。最后战胜了董卓。 | ||
| 122 | + | ||
| 123 | + ## 内奸登场-撤退 | ||
| 124 | + | ||
| 125 | + 因为我们前期的设定,孙坚作为内奸,早已与反贼董卓私下沟通好了,不攻打董卓。 | ||
| 126 | + | ||
| 127 | + - 刘备决定**进攻**,通过信使告诉曹操和孙坚**进攻**。 | ||
| 128 | + | ||
| 129 | + - 曹操决定**撤退**,通过信使告诉曹操和孙坚**撤退**。 | ||
| 130 | + | ||
| 131 | + - 孙坚决定**撤退**,通过信使告诉曹操和刘备**撤退**。 | ||
| 132 | + | ||
| 133 | +  | ||
| 134 | + | ||
| 135 | + 刘备收到进攻和撤退各一票,而自己又选择撤退,所以刘备得到的票数是:进攻 : 撤退 = 1 : 2,遵从少数服从多数的原则,刘备选择最后选择撤退,那么三方的作战方案都是撤退,所以也是一个**一致性**的作战方案。 | ||
| 136 | + | ||
| 137 | + ## 内奸使诈-一进一退 | ||
| 138 | + | ||
| 139 | + 内奸看了上述计划,发现忠臣都撤退了,并没有被消灭,就想通过使诈的方式来消灭其中一个忠臣。 | ||
| 140 | + | ||
| 141 | + - 刘备决定进攻,通过信使告诉曹操和孙坚**进攻**。 | ||
| 142 | + | ||
| 143 | + - 曹操决定撤退,通过信使告诉曹操和孙坚**撤退**。 | ||
| 144 | + | ||
| 145 | + - 孙坚作为内奸使诈,通过信使告诉刘备**进攻**,告诉曹操**撤退**。 | ||
| 146 | + | ||
| 147 | +  | ||
| 148 | + | ||
| 149 | + 那么结果是什么呢? | ||
| 150 | + | ||
| 151 | + 刘备的票数为**进攻** 2 票,**撤退** 1 票,曹操的票数为**进攻** 1 票,**撤退** 2 票。按照少数服从多数的原则,刘备最后会选择进攻,而曹操会选择撤退,孙坚作为内奸肯定不会进攻,刘备单独进攻反贼董卓,势单力薄,被董卓干掉了。 | ||
| 152 | + | ||
| 153 | + 从这个场景中,我们看到内奸孙坚通过发送误导信息,非常容易地就干扰了刘备和曹操的作战计划,导致两位忠臣被逐一击破。这个现象就是**二忠一判**难题。那么主公袁绍该怎么解决这个问题? | ||
| 154 | + | ||
| 155 | + ## 拜占庭问题解法 | ||
| 156 | + | ||
| 157 | + ### 解法原理 | ||
| 158 | + | ||
| 159 | + 就是讲袁绍也参与进来进行投票,这样就增加了一位忠臣的数量。三个忠臣一个叛贼。然后 4 位将军做了一个约定,如果没有收到命令,则执行默认命令,比如撤退。另外约定流程来发送作战信息和如何执行作战指令。这个解法的关键点就是执行两轮作战信息协商。 | ||
| 160 | + | ||
| 161 | + 我们来看下**第一轮**是怎么做的。 | ||
| 162 | + | ||
| 163 | + - 第一步:先发送作战信息的将军我们把他称为**指挥官**(袁绍),另外的将军我们称作**副官**(刘备,曹操,孙坚)。 | ||
| 164 | + - 第二步:**指挥官**将他的作战信息发送给所有的副官。 | ||
| 165 | + - 第三步:每一位**副官**将从**指挥官**处收到的作战信息,作为自己的作战指令;假如没有收到**指挥官**的作战信息,将把默认的撤退作为作战指令。 | ||
| 166 | + | ||
| 167 | + 我们用图来演示:袁绍作为主公先发送作战信息,作战指令为**进攻**。然后曹操、刘备、孙坚收到**进攻**的作战指令。 | ||
| 168 | + | ||
| 169 | +  | ||
| 170 | + | ||
| 171 | + 再来看下**第二轮**是怎么做的。 | ||
| 172 | + | ||
| 173 | + - 第一轮**指挥官**(袁绍)已经发送指令了,现在就需要刘备、曹操、孙坚依次作为**指挥官**给其他两位**副将**发送作战信息。 | ||
| 174 | + - 然后这三位副将按照少数服从多数的原则,执行收到的作战指令。 | ||
| 175 | + | ||
| 176 | + ### 孙坚使诈 - 两撤退 | ||
| 177 | + | ||
| 178 | + 如果孙坚使诈,比如给曹操和刘备都发送撤退信息,如下图所示。那么刘备和曹操收到的作战信息为 进攻 2票,撤退 1 票,按照少数服从多数的原则,最后刘备和曹操执行进攻,实现了作战计划的一致性,曹操和刘备联合作战击败了反贼董卓(即使孙坚没有参加作战。) | ||
| 179 | + | ||
| 180 | +  | ||
| 181 | + | ||
| 182 | + ### 孙坚使诈 - 一进一退 | ||
| 183 | + | ||
| 184 | + 假如孙坚使诈,给曹操发送撤退指令,给刘备发送进攻指令,那么刘备收到的作战信息是进攻 3票,肯定会发起进攻了,而曹操收到的作战信息是进攻 2 票,撤退 1 票,最后曹操还是会进攻,所以刘备和曹操还是联合作战击败了反贼董卓。 | ||
| 185 | + | ||
| 186 | + 如此看来,引入了一位指挥官后,确实可以避免孙坚使诈,但如果是孙坚在第一轮作为指挥官,其他人作为副官呢? | ||
| 187 | + | ||
| 188 | +  | ||
| 189 | + | ||
| 190 | + ### 孙坚作为指挥官 | ||
| 191 | + | ||
| 192 | + 第一轮孙坚向其中一个副官袁绍发送**撤退**指令,向另外两个副官曹操、刘备发送**进攻**指令。那么第一轮的结果如下图: | ||
| 193 | + | ||
| 194 | +  | ||
| 195 | + | ||
| 196 | + | ||
| 197 | + | ||
| 198 | + 第二轮孙坚休息,其他副官按照孙坚发送的指令开始向另外的副官发送指令。 | ||
| 199 | + | ||
| 200 | + - 曹操向刘备和袁绍发送**进攻**指令。 | ||
| 201 | + - 刘备向曹操和袁绍发送**进攻**指令。 | ||
| 202 | + - 袁绍向曹操和刘备发送**撤退**指令。 | ||
| 203 | + | ||
| 204 | + 如下图所示,最后曹操、刘备、袁绍收到的指令为进攻 2 票,撤退 1 票,按照少数服从多数原则,三个人都是发起进攻。执行了一致的作战计划,保证作战的胜利。 | ||
| 205 | + | ||
| 206 | +  | ||
| 207 | + | ||
| 208 | + ### 小结 | ||
| 209 | + | ||
| 210 | + 通过上面的演示,我们知道了如何解决拜占庭将军问题。其实兰伯特在他的论文中也提到过如何解决。 | ||
| 211 | + | ||
| 212 | + > 如果叛将人数为 m,将军数 n >= 3m + 1,那么就可以解决拜占庭将军问题。 | ||
| 213 | + > | ||
| 214 | + > 前提条件:叛将数 m 一致,需要进行 m + 1 轮的作战协商。 | ||
| 215 | + | ||
| 216 | + 这个公式,大家只需要记住就可以了,推到过程可以参考论文。 | ||
| 217 | + | ||
| 218 | + 比如上述的攻打董卓问题,曹操、刘备、孙坚三个人当中,孙坚是叛将,它可以使诈,使作战计划不统一。必须增加一位忠臣袁绍来协商共识,才能达成一致性作战计划。 | ||
| 219 | + | ||
| 220 | + ## 拜占庭解法二-签名 | ||
| 221 | + | ||
| 222 | + 那可以在不增加忠臣的情况下,解决拜占庭的二忠一判问题呢? | ||
| 223 | + | ||
| 224 | + 解法二就是通过签名消息。比如将军之间通过印章、虎符等信物进行通信。来保证这几个特征: | ||
| 225 | + | ||
| 226 | + - 签名无法伪造,对签名消息的内容进行任何更改都会被发现。 | ||
| 227 | + - 任何人都能验证将军签名的真伪。 | ||
| 228 | + | ||
| 229 | + 限于篇幅原因,签名的演示这里就不做展开了,感兴趣的@我,后续会加上。 | ||
| 230 | + | ||
| 231 | + ## 总结 | ||
| 232 | + | ||
| 233 | + 通过三国杀角色来讲解分布式中共识场景。那他们和分布式系统的映射关系是怎么样的呢? | ||
| 234 | + | ||
| 235 | + - 将军对应计算机节点。 | ||
| 236 | + - 忠臣的将军对应正常运行的计算机节点。 | ||
| 237 | + - 叛变的将军对应出现故障并会发送误导信息的计算机节点。 | ||
| 238 | + - 信使被杀对应通讯故障、信息丢失。 | ||
| 239 | + - 信使被间谍替换对应为通讯被恶意攻击、伪造信息或劫持通讯。 | ||
| 240 | + | ||
| 241 | + 可不要小瞧拜占庭问题,它可是分布式场景最复杂的的故障场景。比如在数字货币的区块链技术中就有用到这些知识点。而且必须使用**拜占庭容错**算法(也就是 Byzantine Fault Tolerance,`BFT`)。 | ||
| 242 | + | ||
| 243 | + 拜占庭容错算法还有 `FBFT` 算法,`PoW` 算法,当然不会在这篇中去讲这些算法,后续再讲解。一口吃不了大胖子~ | ||
| 244 | + | ||
| 245 | + 有了拜占庭容错算法,肯定有**非拜占庭容错**算法,顾名思义,就是没有发送误导信息的节点。`CFT` 算法就是解决分布式系统中存在故障,但不存在恶意节点的场景下的共识问题。简单来说就是可能因系统故障造成丢失消息或消息重复,但不存在错误消息、伪造消息。对应的算法有 `Paxos` 算法、`Raft` 算法、`ZAB` 协议。后续讲解~ | ||
| 246 | + | ||
| 247 | + 上面提到了 5 种算法,居然都是跟拜占庭问题有关,你说今天讲的拜占庭问题重要不重要? | ||
| 248 | + | ||
| 249 | + **这么多算法该如何选择?** | ||
| 250 | + | ||
| 251 | + 节点可信,选非拜占庭容错算法。否则就用拜占庭容错算法,如区块链中用到的 PoW 算法。 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,69 @@ | |||
| 1 | + 张三丰教我的分布式太极拳,我全忘了 | ||
| 2 | + | ||
| 3 | + > 背景:元朝赵敏郡主携带一帮高手围攻武当,武当派掌门张三丰被暗算,传了一套武功给张无忌用来对付赵敏。这套武功就是太极拳。 | ||
| 4 | + > | ||
| 5 | + > 张三丰:无忌,你可记得多少招式? | ||
| 6 | + > | ||
| 7 | + > 张无忌:我全忘了! | ||
| 8 | + > | ||
| 9 | + > 张三丰:很好,你只要记住把玄冥二老打趴下就可以了。 | ||
| 10 | + | ||
| 11 | + 上篇用三国杀讲分布式中的拜占庭将军问题,还挺有意思的,这次我们用太极拳来聊下剩下的三大理论: | ||
| 12 | + | ||
| 13 | + - CAP 理论 | ||
| 14 | + - ACID 理论 | ||
| 15 | + - BASE 理论 | ||
| 16 | + | ||
| 17 | + 太极拳的精髓:以柔克刚,刚柔并进,四两拨千斤,无招胜有招。 | ||
| 18 | + | ||
| 19 | + 我把 CAP 理论称作`太极`,ACID 理论成为`阳`或`刚`,BASE 理论称为`阴`或`柔`。ACID 理论追求一致性,BASE 理论本来就叫做柔性事务,追求的是可用性。那张无忌为什么会全忘了还打败了玄冥二老呢?因为太极拳的精髓拳意,无招胜有招。 | ||
| 20 | + | ||
| 21 | + ## 刚或柔是个问题 | ||
| 22 | + | ||
| 23 | + CAP 理论是对分布式系统的特性做了一个高度的抽象,变成了三大指标: | ||
| 24 | + | ||
| 25 | + - 一致性(Consistency) | ||
| 26 | + - 可用性(Availability) | ||
| 27 | + - 分区容错性(Partition Tolerance) | ||
| 28 | + | ||
| 29 | + 分布式中的一致性,我们可以理解为客户端的每次`读操作`,不管访问的是哪个几点,要么督导的都是同一份最新写入的数据,要么读取失败。这就很刚了,不能说这种`刚`不好,在很多场景中,也确实需要保证高度的一致性。 | ||
| 30 | + | ||
| 31 | + 为了帮助大家理解一致性,我举个`三国`的故事: | ||
| 32 | + | ||
| 33 | + 刘备作为主公,带领关羽和张飞攻打曹操,最开始的进攻策略是从北边进攻。刘备发现从北边进攻不妙,于是先给关羽军队下达进从南边进攻的命令,但是关羽和刘备没有告诉张飞的军队,要从南边进攻,刘备次日看到关羽从北边进攻,张飞从南边进攻,这不就乱套了吗?如下图所示: | ||
| 34 | + | ||
| 35 | +  | ||
| 36 | + | ||
| 37 | + 那放到分布式系统中该如何理解呢? | ||
| 38 | + | ||
| 39 | + - 初始环境:客户端查询或更新节点 1 和 节点 2,两个节点存的值 A = 1。 | ||
| 40 | + | ||
| 41 | +  | ||
| 42 | + | ||
| 43 | + - 客户端更新节点 1 中 A 的值,设置 A = 5。 | ||
| 44 | + | ||
| 45 | +  | ||
| 46 | + | ||
| 47 | + - 节点 1 将 A 的值更新为 5 后,返回更新成功给客户端。 | ||
| 48 | + | ||
| 49 | +  | ||
| 50 | + | ||
| 51 | + - 客户端访问到了节点 2 ,请求获取 A 的值,结果返回 A = 1。这和节点 1 中存储的 A 的值就不一致了。 | ||
| 52 | + | ||
| 53 | +  | ||
| 54 | + | ||
| 55 | + - 那么怎么保证两个节点中的值都是 A = 5 呢?客户端将节点 1 更新后,节点 2 也需要更新,才能告诉客户端更新成功了。 | ||
| 56 | + | ||
| 57 | +  | ||
| 58 | + | ||
| 59 | + - 两个节点都更新成功后,客户端访问其中任意一个节点获取到的都是 A = 5。这个就叫做一致性。 | ||
| 60 | + | ||
| 61 | +  | ||
| 62 | + | ||
| 63 | + 一致性强调的是数据正确,每次读取节点中的数据都是最新写入的数据。但是 | ||
| 64 | + | ||
| 65 | + ## 刚柔并进 | ||
| 66 | + | ||
| 67 | + | ||
| 68 | + | ||
| 69 | + ## 无招胜有招 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments