🎯 什麼情境該想到我
當你在分散式系統裡糾結「要不要強一致、可用性 vs 一致性怎麼取捨、多節點怎麼對同一件事達成一致」時。
⚙️ 怎麼用
- 線性一致性(Linearizability):讓多副本「看起來像只有一份資料」,讀到的一定是最新已寫入值。強、直覺,但成本高。
- CAP 取捨:網路分區(P)發生時,只能在一致性(C)與可用性(A)間擇一。分區無法避免 → 你在選 CP 還是 AP。
- 共識(Consensus):讓一群節點對某個值達成一致(選主、鎖、原子提交)。用 Raft/Paxos 這類演算法,或直接靠 ZooKeeper/etcd。
- 能不要強一致就別要:多數場景用因果一致性/最終一致性即可,成本低很多。
★ CAP 常被誤解,正確的用法是這樣
CAP 不是「三選二」,是「分區發生時,C 和 A 只能選一個」。
網路分區無法避免(光纖會斷、交換器會壞、GC 會停頓),所以 P 是前提不是選項。
★ 沒有分區的時候,你可以同時擁有 C 和 A —— 這是 CAP 最常被忽略的一半。
更實用的框架是 PACELC:
if (Partition) then C or A ← CAP 講的
else L or C ← ★ 平時:Latency 或 Consistency
★ else 那一半才是你每天在付的成本:
強一致要跨節點確認 → 即使一切正常,延遲也更高。
多數系統選 AP/EL(可用 + 低延遲),這是刻意的,不是妥協。
★ 一致性模型的光譜(越上面越強、越貴)
| 模型 | 保證 | 成本 |
|---|---|---|
| 嚴格可序列化 | 線性一致 + 可序列化 | 最高 |
| 線性一致(Linearizability) | ★ 看起來像只有一份資料,讀到的一定是最新已提交值 | 高(要共識) |
| 可序列化(Serializable) | 交易像一個個依序執行(★ 但不保證「順序和真實時間一致」) | 高 |
| 因果一致 | ★ 有因果關係的操作順序一致,無關的可以亂序 | 中——CP 難以企及時的好折衷 |
| 最終一致 | 不再有寫入時,最終會收斂 | 低 |
★ 「線性一致」和「可序列化」是不同的東西,常被混用:
線性一致是關於單一物件 + 真實時間順序(recency);
可序列化是關於多物件交易 + 某種等價順序(不保證是真實時間順序)。
兩個都要 = 嚴格可序列化(例如 Spanner)。
★ 什麼時候真的需要線性一致(很少)
| 需要 | 不需要 |
|---|---|
| ★ 選主 / leader election | 社群動態、按讚數 |
| ★ 唯一性約束(帳號名、座位、庫存扣減到 0) | 推薦、搜尋 |
| ★ 分散式鎖/柵欄 | 快取 |
| 跨通道的因果(付款完成 → 觸發出貨) | 遙測、日誌 |
| 帳務餘額 | 「大約多少人在線」 |
★ 判準:這件事「錯了會怎樣」?
錯了會造成重複扣款、超賣、兩個 leader → 需要強一致。
錯了只是「數字晚幾秒更新」→ 最終一致就好,而且便宜非常多。
★★ 分散式鎖的陷阱(實務上最常出事的地方)
客戶端 A 取得鎖 → ★ GC 停頓 15 秒 → 鎖過期 → 客戶端 B 取得鎖
↓
A 醒來,「以為」自己還持有鎖 → 兩個人同時寫 → ★ 資料毀損
★ 鎖有過期時間 + 程序可能任意暫停 = 鎖永遠不安全。
SETNX+ TTL 的 Redis 鎖擋不住這個問題(Redlock 的爭議核心也在這裡)。
唯一的正解:fencing token(柵欄令牌)
取得鎖時拿到一個「單調遞增的編號」 → 每次寫入都帶上它
★ 儲存端「拒絕」編號比已見過的更小的寫入
→ A 醒來用舊 token 寫入 → 被拒絕
★ 關鍵:保護必須在「被保護的資源」那一端做,不能只靠客戶端自律。
這需要儲存端支援(或用資料庫的條件更新 WHERE version = ? 達成同樣效果,
見 工具-交易與隔離等級 的樂觀鎖)。
★ 共識演算法:要知道什麼
| 演算法 | 特點 |
|---|---|
| Paxos | 理論基礎,★ 極難正確實作 |
| Raft | ★ 為「可理解性」設計:leader election + log replication + safety 三塊分開 |
| ZAB | ZooKeeper 用的 |
| Viewstamped Replication | 與 Raft 類似 |
共同性質:
- 需要多數(quorum): 個節點容忍 個故障 → ★ 所以節點數是奇數(3、5、7)
- ★ 節點越多越慢(每次都要多數確認),不是越多越好
- 只容忍「崩潰故障」,不容忍拜占庭故障(惡意節點)
★★ 實務結論:不要自己實作共識演算法。
用 etcd、ZooKeeper、Consul,或直接用資料庫的交易。
自己實作 Raft 是一個「看起來對、但在網路分區與時鐘異常時會靜默出錯」的經典陷阱。
🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ 不要自己實作共識(見上)。用成熟的元件。
- ★ 不要把「強一致」當預設。它的成本是每次操作的延遲 + 分區時的不可用。
先問「這裡錯了會怎樣」,多數答案是「沒怎樣」。 - ★ 分散式鎖要有 fencing token,否則它只是「大部分時候有效的建議」。
- CAP 裡的 A 是「每個非故障節點都能回應」,不是「系統大致可用」。
實務上的可用性討論遠比 CAP 的形式定義寬鬆——不要用 CAP 當作架構決策的唯一依據。 - 時鐘不可信。NTP 會跳、VM 會暫停、閏秒會出事。
★ 不要用 wall clock 判斷「誰比較新」(LWW 的根本問題,見 工具-資料複製)——
用邏輯時鐘(Lamport、vector clock)或單調時鐘。 - 「網路分區」包含 GC 停頓與 VM 暫停。一個 stop-the-world GC 讓節點靜默 10 秒,
從外面看和「網路斷了」無法區分。 - 共識系統的資料量要小。etcd/ZooKeeper 適合存「設定與協調資訊」,不是資料庫。
🔗 相關工具
- 工具-資料複製 —— 大多數系統選的那一側(最終一致)
- 工具-交易與隔離等級 —— 單機內的一致性與樂觀鎖
- 工具-分散式事務的取捨 —— 跨服務時的實務選擇
- 工具-故障偵測 —— 「掛了還是只是慢」的根本難題
- 工具-一致性雜湊 —— 無共識的資料分配
- 工具-服務容錯設計 —— 分區時的降級策略
- 設計資料密集型應用