一致性與共識:linearizability、CAP 的誠實版,與全序廣播

· tech

#distributed-systems#book-notes#consistency

📑 目錄

上一篇的結論是:任何單一節點的判斷都不可信,真相只能由多數決定。這章講的就是「多數怎麼安全地決定」——DDIA 全書的理論高潮。演算法細節(Raft、Paxos、Zab 怎麼投票換屆)我在 SRE 共識那篇拆過了,這篇取 Ch9 獨有的三個台柱:最強的一致性保證長什麼樣、CAP 到底在說什麼、以及「共識」這個玄詞的真身

Linearizability:讓系統「看起來只有一份資料」

一致性保證的天花板叫 linearizability(線性一致),定義可以講得很白話:整個系統表現得像只有「一份」資料,而且每個操作都是原子的——只要任何人讀到了新值,之後所有人都必須讀到新值,不准再看到舊的。DDIA 用一場球賽講它:

終場哨響:結果寫入,但兩個 replica 進度不同 leader:2 : 1 終場 replica 1(已同步):2:1 終場 replica 2(落後):1:1 進行中 ① Alice 查到「終場 2:1」→ 告訴 Bob ② Bob 重新整理 → 「還在踢」?! 發生在「後」的讀,讀到「更舊」的狀態 → 「單一資料」的幻覺破滅 = 不 linearizable
終場結果已寫入,Alice 打到已同步的 replica 看到 2:1、興奮地告訴 Bob;Bob 自己重新整理,卻打到落後的 replica——「還在踢」。注意這不只是 複製延遲怪象的重演:Bob 的讀在時間上發生於 Alice 之後,卻讀到更舊的狀態——「整個系統只有一份資料」的幻覺當場破滅。linearizability 就是守住這個幻覺的保證:只要有人讀到新值,之後所有人都必須讀到新值

哪些事非它不可?清單其實很短:唯一性約束(兩個人同時搶同一個 username、最後一個機位——本質都是「所有人必須對『誰先』有共識」)、leader 選舉(不能有兩個節點都以為自己是 leader)、跨系統的時序依賴。而它貴得驚人:單主同步複製慢、Dynamo 式 quorum 嚴格說也不 linearizable(除非加同步 read repair)、而且分區時你必須犧牲可用性——這就接到了那個被講爛的定理。

CAP 的誠實版:分區不是讓你「選」的

CAP 常被講成「一致性、可用性、分區容忍,三選二」——DDIA 對此很不客氣,而它的批評值得原文精神照登:網路分區(P)不是一個你可以不選的選項,它是一種「會發生」的故障。 你不能「選擇不要分區」,就像不能選擇不要地震。所以真正的取捨是:

  • 分區發生時:你只能在 C(拒絕服務以保一致)和 A(繼續服務但可能不一致)之間選——這是 CAP 唯一說了的事。
  • 沒有分區的平時(絕大多數時間):CAP 什麼都沒說;你真正在權衡的是一致性 vs 延遲(linearizable 的讀寫要跨節點協調,慢)。

所以「我們是 AP 系統」「那是 CP 資料庫」這種粗標籤,多半經不起追問——同一個系統的不同操作常常落在不同點上。比起背三個字母,不如問兩個具體問題:分區的那幾分鐘你要保什麼?平時你願意為多強的一致付多少延遲?

共識的真身:全序廣播,就是「大家同意同一條 log」

「共識」聽起來玄,DDIA 給了它一個工程師秒懂的等價形式——全序廣播(total order broadcast):所有節點以同樣的順序收到同樣的一串訊息,不丟不重。而這跟共識是同一個問題:對訊息順序達成一致 = 反覆地做共識(第 1 筆是什麼?第 2 筆是什麼?…)。它的力量在於:

各方寫入請求 x=1 y=2 x=3 共識模組 把訊息排成大家同意的順序 一條「全序」log(所有人同意) ① x=1 → ② y=2 → ③ x=3 節點 1:照序套用x=3, y=2 節點 2:同序x=3, y=2 節點 3:同序x=3, y=2 同一起點 + 同一順序 + 確定性套用 = 狀態必然相同(狀態機複製) 同一個形狀:Raft 的 replicated log · Kafka 單一 partition 的全序 · 資料庫的複製串流
各方的寫入先經過共識模組,被排成一條所有人都同意順序的 log;每個節點照同一順序、確定性地逐筆套用——起點相同 + 序列相同,狀態必然相同(狀態機複製)。這就是共識的工程真身:不是投票的儀式,是「大家共享同一條 log」。你早就見過這個形狀:Raft 的 replicated log、Kafka 單一 partition 內的全序、資料庫的複製串流——ZooKeeper/etcd 本質上就是「一條被過半保護的 log + 一台狀態機」

幾個常見的「假共識」也在這章現形:2PC 不是共識——協調者在 prepare 之後掛掉,所有參與者卡死等待(blocking),單點沒了整局停擺;共識演算法正是靠過半 + 可以換 leader 治好這個病。Lamport 時間戳能事後排出全序,卻不能當下回答「這個 username 給不給」——要即時定案,還是得共識。而共識演算法裡的 epoch / term 編號(防舊 leader 醒來搗亂),你應該覺得眼熟——它就是 fencing token 的親戚:又是單調遞增的數字 + 過半,分散式的終極答案永遠這兩味藥。

反思

需要 linearizability 的地方,比你以為的少一個數量級

這章先把最強保證講得誘人,再誠實告訴你它多貴——而我的實戰結論是:真正非 linearizable 不可的,幾乎只有「唯一性」和「誰是 leader」兩類,它們的共同點是「全世界必須對一個裁決立刻一致」。其他場景,一致性菜單上便宜的那幾道(read-your-writes、因果)幾乎都夠。這也讓我對「我們系統要強一致」的需求會多問一句:是哪個操作、什麼裁決需要? 十次有八次,挖下去只剩一個唯一性約束——那就把貴的保證圈在那一小塊(交給資料庫 unique index 或 協調服務),其餘放寬。一致性跟安全庫存一樣,全域拉滿是浪費,關鍵位置備足才是本事。

「共識=一條大家同意的 log」——這個等價讓我把五個系統看成一個

全序廣播 ≡ 共識,是我讀 DDIA 收穫最大的一個「啊哈」。共識從「神秘的投票演算法」變成一句話:大家共享同一條順序不容爭議的 log,然後各自照抄。 這一下打通了我地圖上五個原本孤立的點:Raft 的 replicated log、ZooKeeper 的 zxid 序列、Kafka partition 的 offset 全序、資料庫的 WAL 複製串流、甚至 Redis 的複製流——全是「一條 log + 照序套用」的形狀,差別只在那條 log 被保護得多嚴(過半共識、單一 leader、還是盡力而為)。認出這個形狀後,新系統的複製文件我幾乎能用猜的:先找它的 log 在哪、誰決定順序、順序被什麼保護。一個等價關係,勝過十份架構文件。

CAP 教我的不是理論,是「拒絕粗標籤」的紀律

DDIA 對 CAP 的批評,我讀完最大的收穫是一種提問的紀律。「我們是 AP 系統」這種話,在架構會議上聽起來專業,實際上什麼都沒說——分區時哪個操作降級?怎麼降?平時為一致性付了多少延遲?這跟我一路的體會合流:一致性是菜單不是開關隔離層級是光譜不是布林——分散式的重要性質幾乎都是「按操作、分等級」的,任何把它壓成一個字母的說法,都是在逃避真正的設計決策。 現在聽到三字母定理被搬出來,我就問兩個問題:分區的那幾分鐘你保什麼?平時你付多少延遲?答得出來,才算真的想過。DDIA Part II 到此完結——網路會斷、時鐘會飄、節點會裝死,而我們靠一條被過半保護的 log,在機率的世界裡搭出了確定性的小島。下一部,資料開始在系統之間流動。