用三國殺講分散式演算法,舒適了吧?

ITPUB社群 發表於 2022-11-24


前言

《三國殺》是一款熱門的卡牌遊戲,結合中國三國時期背景,以身份為線索,以卡牌為形式,益智休閒,老少皆宜。

東漢末年,袁紹作為盟主,匯合了十八路諸侯一起攻打董卓。

在講解之前,我們先聊下分散式協議和演算法整體脈絡。

現在很多開發同學對分散式的元件怎麼使用都有一定經驗,也知道 CAP 理論和 BASE 理論的大致含義。但認真去看分散式演算法的真的很少,原因有三

  • 擔心演算法過於複雜,所以花的時間很少。
  • 網上的資料能用大白話將分散式演算法講清楚的比較少。
  • 學習分散式演算法沒有一條清晰的路線。

學習路線

學習分散式協議和演算法的路線可以是先學習四大基礎理論,作為地基,再學習分散式協議和演算法,就像是在地基上建房子。地基打好了,才能建更穩固的高樓大廈。

四大基礎理論

  • 拜占庭將軍問題
  • CAP 理論
  • ACID 理論
  • BASE 理論

八大分散式協議和演算法

  • Paxos 演算法
  • Raft 演算法
  • 一致性 Hash 演算法
  • Gossip 協議演算法
  • Quorum NWR 演算法
  • FBFT 演算法
  • POW 演算法
  • ZAB 協議

因篇幅原因,本篇只涉及拜占庭將軍問題。

拜占庭將軍問題

大家可能聽過拜占庭將軍問題。它是由萊斯利·蘭伯特提出的點對點通訊中的基本問題,

拜占庭位於如今的土耳其的伊斯坦布林,是東羅馬帝國的首都。由於當時拜占庭羅馬帝國國土遼闊,為了達到防禦目的,每個軍隊都分隔很遠,將軍與將軍之間只能靠信差傳訊息。在戰爭的時候,拜占庭軍隊內所有將軍副官必須達成一致的共識,決定是否有贏的機會才去攻打敵人的陣營。但是,在軍隊內有可能存有叛徒和敵軍的間諜,這個就是拜占庭容錯問題

實際上拜占庭問題是分散式領域最複雜的一個容錯模型,一旦理解它,就能掌握分散式共識問題的解決思路,還能幫助大家理解常用的共識演算法,也可以幫助我們在工作中選擇合適的演算法,或者設計合適的演算法。

為什麼第一個基礎理論是拜占庭將軍問題?

因為它很好地抽象出了分散式系統面臨的共識問題。 上面提到的 8 種分散式演算法中有 5 種跟拜占庭問題相關,可以說弄懂拜占庭問題對後面學習其他演算法就會容易很多。

下面我用三國殺遊戲中的身份牌來講解拜占庭將軍問題。

三國殺身份牌

三國殺中主要有四種身份:主公、忠臣、反賊、內奸。每個遊戲玩家都會獲得一個身份牌。主公只有 1 個。忠臣 最多 2 個,反賊最多 4個,內奸最多一個。

主公

用三國殺講分散式演算法,舒適了吧?

獲勝條件: 消滅所有反賊和內奸

技巧: 以自己生存為首要目標,分散反賊注意力。配合忠內剿滅反賊並判斷誰是忠誰是內。

忠臣

用三國殺講分散式演算法,舒適了吧?

獲勝條件:保護主公存活的前提下消滅所有反賊和內奸。

技巧:忠臣是主公的屏障,威懾反賊和內奸的天平。

反賊


用三國殺講分散式演算法,舒適了吧?

獲勝條件:消滅主公即可獲勝。

技巧: 反賊作為數量最多的身份,需要集中火力猛攻敵人弱點。正確的思路是獲勝的關鍵。

內奸

用三國殺講分散式演算法,舒適了吧?

獲勝條件: 先消滅反賊和忠臣,最後與主公單挑成為最後唯一生還者。

技巧:正確的戰術+ 冷靜的頭腦+ 運氣。

還原拜占庭問題

東漢末年,袁紹作為盟主,匯合了十八路諸侯一起攻打董卓。把董卓定為反賊,袁紹定為主公,另外有兩個忠臣和一個內奸,就選這三個風雲人物:曹操,劉備,孫堅(孫權的爸比),內奸扮演的角色是忠臣,主公和兩個忠臣不知道內奸的身份,都當作忠臣對待了。

用三國殺講分散式演算法,舒適了吧?

董卓是非常強大的,擁有精良的西涼兵,麾下還有戰神呂布。大家都知道三英戰呂布的故事,呂布以一已之力對陣劉備、張飛、關羽三人。

要想幹掉董卓,袁紹必須統一忠臣的作戰計劃,三位忠臣還不知道有什麼其他花花腸子,有一個還是內奸。如果內奸暗通反賊董卓,給忠臣傳送誤導性的作戰資訊,該怎麼辦?另外假定這幾個忠臣都是透過書信交流作戰資訊,如果書信被攔截了或書信裡面的資訊被替換了咋辦?這些場景都可能擾亂作戰計劃,最後出現有的忠臣在進攻,有的忠臣撤退了。那麼反賊就可以乘此機會發起進攻,逐一攻破。

袁紹本來就沒有曹操的機智,那他如何讓忠臣們達成共識,制定統一的作戰計劃呢?

上面的對映關係就是一個拜占庭將軍問題的一個簡化表述,袁紹現在面臨的就是典型的共識問題。也就是在可能有誤導資訊的情況下,採用合適的通訊機制,讓多個將軍達成共識,制定一致性的作戰計劃。

一方選擇撤退

劉備、曹操、孫堅透過信使傳遞進攻或撤退的資訊,然後進行協商,到底是進攻還是撤退。遵循少數服從多數,不允許棄權。

曹操疑心比較重,偵查了反賊的地形後,決定撤退。而劉備和孫堅決定進攻。

  • 劉備決定進攻,透過信使告訴曹操和孫堅進攻

  • 曹操決定撤退,透過信使告訴劉備和孫堅撤退

  • 孫堅決定進攻,透過信使告訴曹操和劉備進攻

用三國殺講分散式演算法,舒適了吧?

曹操收到的資訊:進攻 2 票,自己的一張撤退票,票數一比,進攻票:撤退票 = 2 : 1,按照上面的少數服從多數原則進行投票表決,曹操還是會進攻。那麼三方的作戰方案都是進攻,所以是一個一致性的作戰方案。最後戰勝了董卓。

內奸登場-撤退

因為我們前期的設定,孫堅作為內奸,早已與反賊董卓私下溝通好了,不攻打董卓。

  • 劉備決定進攻,透過信使告訴曹操和孫堅進攻

  • 曹操決定撤退,透過信使告訴曹操和孫堅撤退

  • 孫堅決定撤退,透過信使告訴曹操和劉備撤退

用三國殺講分散式演算法,舒適了吧?

劉備收到進攻和撤退各一票,而自己又選擇撤退,所以劉備得到的票數是:進攻 : 撤退 = 1 : 2,遵從少數服從多數的原則,劉備選擇最後選擇撤退,那麼三方的作戰方案都是撤退,所以也是一個一致性的作戰方案。

內奸使詐-一進一退

內奸看了上述計劃,發現忠臣都撤退了,並沒有被消滅,就想透過使詐的方式來消滅其中一個忠臣。

  • 劉備決定進攻,透過信使告訴曹操和孫堅進攻

  • 曹操決定撤退,透過信使告訴劉備和孫堅撤退

  • 孫堅作為內奸使,透過信使告訴劉備進攻告訴曹操撤退

用三國殺講分散式演算法,舒適了吧?

那麼結果是什麼呢?

劉備的票數為進攻 2 票,撤退 1 票,曹操的票數為進攻 1 票,撤退 2 票。按照少數服從多數的原則,劉備最後會選擇進攻,而曹操會選擇撤退,孫堅作為內奸肯定不會進攻,劉備單獨進攻反賊董卓,勢單力薄,被董卓幹掉了。

從這個場景中,我們看到內奸孫堅透過傳送誤導資訊,非常容易地就干擾了劉備和曹操的作戰計劃,導致兩位忠臣被逐一擊破。這個現象就是二忠一判難題。那麼主公袁紹該怎麼解決這個問題?

拜占庭問題解法

解法原理

就是將袁紹也參與進來進行投票,這樣就加了一位忠臣的數量。三個忠臣一個叛賊。然後 4 位將軍做了一個約定,如果沒有收到命令,則執行預設命令,比如撤退。另外約定流程來傳送作戰資訊和如何執行作戰指令。這個解法的關鍵點就是執行兩輪作戰資訊協商。

我們來看下第一輪是怎麼做的。

  • 第一步:先傳送作戰資訊的將軍我們把他稱為指揮官(袁紹),另外的將軍我們稱作副官(劉備,曹操,孫堅)。
  • 第二步:指揮官將他的作戰資訊傳送給所有的副官。
  • 第三步:每一位副官將從指揮官處收到的作戰資訊,作為自己的作戰指令;假如沒有收到指揮官的作戰資訊,將把預設的撤退作為作戰指令。

我們用圖來演示:袁紹作為主公先傳送作戰資訊,作戰指令為進攻。然後曹操、劉備、孫堅收到進攻的作戰指令。

用三國殺講分散式演算法,舒適了吧?

再來看下第二輪是怎麼做的。

  • 第一輪指揮官(袁紹)已經傳送指令了,現在就需要劉備、曹操、孫堅依次作為指揮官給其他兩位副將傳送作戰資訊。
  • 然後這三位副將按照少數服從多數的原則,執行收到的作戰指令。

孫堅使詐 - 兩撤退

如果孫堅使詐,比如給曹操和劉備都傳送撤退資訊,如下圖所示。那麼劉備和曹操收到的作戰資訊為 進攻 2票,撤退 1 票,按照少數服從多數的原則,最後劉備和曹操執行進攻,實現了作戰計劃的一致性,曹操和劉備聯合作戰擊敗了反賊董卓(即使孫堅沒有參加作戰。)

用三國殺講分散式演算法,舒適了吧?

孫堅使詐 - 一進一退

假如孫堅使詐,給曹操傳送撤退指令,給劉備傳送進攻指令,那麼劉備收到的作戰資訊是進攻 3票,肯定會發起進攻了,而曹操收到的作戰資訊是進攻 2 票,撤退 1 票最後曹操還是會進攻,所以劉備和曹操還是聯合作戰擊敗了反賊董卓。

如此看來,引入了一位指揮官後,確實可以避免孫堅使詐,但如果是孫堅在第一輪作為指揮官,其他人作為副官呢?

用三國殺講分散式演算法,舒適了吧?

孫堅作為指揮官

第一輪孫堅向其中一個副官袁紹傳送撤退指令,向另外兩個副官曹操、劉備傳送進攻指令。那麼第一輪的結果如下圖:

用三國殺講分散式演算法,舒適了吧?

第二輪孫堅休息,其他副官按照孫堅傳送的指令開始向另外的副官傳送指令。

  • 曹操向劉備和袁紹傳送進攻指令。
  • 劉備向曹操和袁紹傳送進攻指令。
  • 袁紹向曹操和劉備傳送撤退指令。

如下圖所示,最後曹操、劉備、袁紹收到的指令為進攻 2 票,撤退 1 票,按照少數服從多數原則,三個人都是發起進攻。執行了一致的作戰計劃,保證作戰的勝利。

用三國殺講分散式演算法,舒適了吧?

小結

透過上面的演示,我們知道了如何解決拜占庭將軍問題。其實蘭伯特在他的論文中也提到過如何解決。

如果叛將人數為 m,將軍數 n >= 3m + 1,那麼就可以解決拜占庭將軍問題。

前提條件:叛將數 m 已知,需要進行 m + 1 輪的作戰協商。

這個公式,大家只需要記住就可以了,推到過程可以參考論文。

比如上述的攻打董卓問題,曹操、劉備、孫堅三個人當中,孫堅是叛將,他可以使詐,使作戰計劃不統一。必須增加一位忠臣袁紹來協商共識,才能達成一致性作戰計劃。

拜占庭解法二-簽名

那可以在不增加忠臣的情況下,解決拜占庭的二忠一判問題呢?

解法二就是透過簽名訊息。比如將軍之間透過印章、虎符等信物進行通訊。來保證這幾個特徵:

  • 簽名無法偽造,對簽名訊息的內容進行任何更改都會被發現。
  • 任何人都能驗證將軍簽名的真偽。

限於篇幅原因,簽名的演示這裡就不做展開了,感興趣的@我,後續會加上。

總結

透過三國殺角色來講解分散式中共識場景。那他們和分散式系統的對映關係是怎麼樣的呢?

  • 將軍對應計算機節點
  • 忠臣的將軍對應正常執行的計算機節點。
  • 叛變的將軍對應出現故障並會傳送誤導資訊的計算機節點。
  • 信使被殺對應通訊故障資訊丟失
  • 信使被間諜替換對應為通訊被惡意攻擊偽造資訊劫持通訊

可不要小瞧拜占庭問題,它可是分散式場景最複雜的的故障場景。比如在數字貨幣的區塊鏈技術中就有用到這些知識點。而且必須使用拜占庭容錯演算法(也就是 Byzantine Fault Tolerance,BFT)。

拜占庭容錯演算法還有 FBFT 演算法,PoW 演算法,當然不會在這篇中去講這些演算法,後續再講解。一口吃不了大胖子~

有了拜占庭容錯演算法,肯定有非拜占庭容錯演算法,顧名思義,就是沒有傳送誤導資訊的節點。CFT 演算法就是解決分散式系統中存在故障,但不存在惡意節點的場景下的共識問題。簡單來說就是可能因系統故障造成丟失訊息或訊息重複,但不存在錯誤訊息、偽造訊息。對應的演算法有 Paxos 演算法、Raft 演算法、ZAB 協議。後續講解~

上面提到了 5 種演算法,居然都是跟拜占庭問題有關,你說今天講的拜占庭問題重要不重要?

這麼多演算法該如何選擇?

節點可信,選非拜占庭容錯演算法。否則就用拜占庭容錯演算法,如區塊鏈中用到的 PoW 演算法。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2924967/,如需轉載,請註明出處,否則將追究法律責任。