分散式共識:一群會掛的機器,如何對同一件事達成一致
· tech
📑 目錄
一群機器要對「同一件事」達成一致——誰是 leader?這把鎖在誰手上?最新的值是多少?——聽起來很簡單,卻是分散式系統最難的問題之一。難在哪?因為機器會掛、網路會斷、時鐘不可信,而你要在這些前提下,讓大家對一個答案有共識,而且永遠不會分歧。
為什麼「自己搞」會出事:split brain
最直覺的土砲解法是:大家互發心跳(heartbeat),誰沒回應就把它當死的、重新選一個 leader。平常沒事,但網路一斷就出事了:
問題的根源是:「對方沒回應」和「對方掛了」,你其實分不清楚——可能只是網路斷了,對方活得好好的。土砲演算法把「收不到」當成「死了」,於是網路分割時,兩邊都得出「我該當 leader」的結論。要根治,需要一個數學上能保證「絕不分歧」的機制。
共識的核心:多數決(quorum)
分散式共識(Paxos、Raft、Zab 是它的幾種實作)靠的其實是一個小學就懂的原理——多數決:任何決定都必須拿到「過半」節點的同意才算數。而過半有一個關鍵性質:
有了多數決,共識系統能給出兩個關鍵保證:安全性(safety)——永遠不會有兩個矛盾的決定,這條在任何情況下都成立;以及活性(liveness)——只要過半節點活著且能通訊,最終一定能有結論。注意安全性是無條件的:就算網路爛到只剩一半能通,系統寧可停下來(不可用)也絕不給出錯誤的答案——這正是 CAP 裡「分割時選一致性」的體現。
同一個問題的三種實作:Paxos、Raft、Zab
「多數決」是原理,但把它變成一個真的能跑、又不會出錯的演算法,細節多到嚇人。共識不是單一演算法,而是一整族;最常聽到的三個名字——Paxos、Raft、Zab——解的是同一題(用過半同意,湊出一份有序的日誌),但個性差很多:
Paxos:理論鼻祖,正確但難搞
Leslie Lamport 在 1989 年提出,是分散式共識的理論源頭,數學上證明過正確。但它惡名昭彰地難懂:原始論文只講「對單一個值達成共識」(basic Paxos),真實系統要的卻是對「一連串值」(一份 log)達成共識的 Multi-Paxos,而這部分論文語焉不詳,於是每家實作長得都不一樣、細節裡全是坑。Google 那篇〈Paxos Made Live〉整篇就在講「從論文到能上線的產品,中間有多少沒寫出來的血淚」。用它的是 Google 的 Chubby、Spanner 這類重量級系統。
Raft:為了「讓人看得懂」而生
2014 年 Stanford 提出,動機直接寫在論文標題:〈In Search of an Understandable Consensus Algorithm〉——它就是受不了 Paxos 太難,刻意設計成好懂。做法是強 leader 模型:所有變更只從 leader 流向 follower(單向),再把問題拆成三塊好啃的子題——leader 選舉、log 複製、安全性。它還有一個很優雅的小招:隨機化的選舉逾時,自然避免大家同時搶著當 leader 的分票僵局。Raft 給的保證跟 Multi-Paxos 一樣,但你真的實作得出來、又不容易錯,所以成了現代預設——etcd、Consul、TiKV、CockroachDB、Kafka 的 KRaft 都用它。
Zab:ZooKeeper 的專用引擎
Zab(ZooKeeper Atomic Broadcast)是 Apache ZooKeeper 背後的協定,比 Raft 更早、風格也神似(同樣強 leader)。它為「協調服務」這個特定場景量身打造,主打原子廣播(atomic broadcast)——保證所有狀態變更在每台機器上都以完全相同的順序套用;leader 用單調遞增的 zxid 幫每筆變更編號定序,並圍繞「primary 崩潰後怎麼乾淨地恢復」做設計。你可能沒直接用過它,但八成間接依賴過——ZooKeeper 撐起了舊版 Kafka、HBase、Hadoop 一大票系統。
共識拿來做什麼:一份大家都同意的日誌
共識最常見的用法,是產出一份所有副本都同意、順序一致的操作日誌(replicated log)。每個副本照同樣的順序套用同樣的操作,狀態自然就一致了——這就是 replicated state machine。在這之上,可以蓋出一堆關鍵設施:
- leader 選舉:永遠只有一個 leader,不會 split brain。
- 分散式鎖:誰真正持有鎖,全叢集有共識。
- 設定 / 中繼資料儲存:整個系統的「真相來源(source of truth)」。
而 SRE 最重要的一句忠告是:別自己造共識輪子。 共識演算法的正確性極其微妙,土砲版幾乎必有隱藏 bug。實務上直接用被驗證過的現成系統——你天天在用的基礎設施,底層幾乎都是它:K8s 把整個叢集狀態託付給 etcd——它走的是 Raft;Kafka 新版的 KRaft 顧名思義也是 Raft;Google 內部則是 Chubby。
反思
「別自己造共識輪子」是我學費換來的信仰
我年輕時真的幹過「用資料庫的一個 flag 欄位 + 定時心跳來選 leader」這種事,當下覺得很聰明、很省。結果就是在一次網路抖動裡,兩個實例同時搶到「我是 leader」,各自跑了一輪本該互斥的任務,收拾了很久。共識這種東西,平常跑一萬次都對,錯的是那第一萬零一次的邊界狀況——而分散式系統裡,罕見的邊界狀況每天都在發生。 從那之後我信一條:凡是牽涉「一群機器要對某件事達成一致」的需求,我一律去找 etcd / ZooKeeper,而不是自己拼。這不是偷懶,是承認一件事——這個問題比它看起來難一個數量級,而別人已經把它解對了。
split brain 教我的:最危險的故障是「大家都自認正確」
split brain 讓我對「故障」的想像變立體了。以前我以為故障就是「東西掛掉、沒回應」,那其實還算好處理——至少你知道它壞了。真正可怕的是 split brain 這種:沒有人掛掉,每個節點都活得好好的、都在正常運作、都基於自己看到的局部資訊做出了「合理」的判斷,然後整體崩壞。 這跟DDIA 講的「部分失效」是同一種陰影——分散式系統的難,往往不在單點壞掉,而在沒有一個上帝視角,每個節點只能看到局部,卻要做出全域一致的決定。多數決的優雅,正在於它用「重疊」這個幾何性質,幫這群各自為政的節點強加了一個唯一的真相。
共識不是免費的,所以要用在刀口上
多數決聽起來很美,但它有代價:每個決定都要等過半節點來回確認,這是實打實的延遲與 throughput 瓶頸。所以好的架構不會把什麼都塞進共識,而是只讓最關鍵、最不能出錯的那一小撮狀態走共識(誰是 leader、鎖、關鍵設定),其餘大量的資料走比較便宜的複製。這也呼應了我一直的體會:可靠度從來不是「哪裡都拉到最高」,而是分清楚哪些地方值得付昂貴的代價、哪些地方不必——把最強的保證,留給真正輸不起的那條線。