一致性模型#
「一致性 (Consistency)」是分散式系統裡最被濫用的詞——它在 ACID、CAP、複製延遲三個語境下意思完全不同。本章把一致性視為一道從強到弱的光譜:愈強的保證愈好推理,但效能與可用性代價愈高;愈弱的保證愈便宜,但把更多複雜度推給應用層。理解光譜,才能為每個業務環節挑對的那一格。
一致性光譜#
flowchart LR
A["線性一致性<br/>(最強)"] --> B["順序 / 因果一致性"]
B --> C["讀己之寫 / 單調讀<br/>(會話保證)"]
C --> D["最終一致性<br/>(最弱)"]
style A fill:#ffccbc
style B fill:#ffe0b2
style C fill:#fff9c4
style D fill:#c8e6c9| 一致性等級 | 保證 | 協調成本 | 典型用途 |
|---|---|---|---|
| 線性一致性 | 全系統像只有一份資料、操作即時生效 | 最高 | 選主、唯一性約束、分散式鎖 |
| 因果一致性 | 有因果關係的操作順序被保證,並行操作不限制 | 中 | 留言串、社群動態 |
| 會話保證 | 單一使用者視角內的弱一致(讀己之寫等) | 低 | 個人化讀取體驗 |
| 最終一致性 | 停止寫入後,副本終將收斂 | 最低 | 快取、計數器、DNS |
線性一致性 (Linearizability)#
線性一致性(又稱原子一致性、強一致性)是最強的單物件保證:系統表現得彷彿只有一份資料,每個操作在它的呼叫與回覆之間的某個瞬間「原子地」生效;一旦某次讀取看到新值,之後所有讀取都必須看到新值(或更新)。
為什麼需要它#
線性一致性是某些功能正確性的前提,弱一致性在這些場景會直接出錯:
- 選主 / 分散式鎖:必須全系統公認「只有一個」主節點,否則腦裂。
- 唯一性約束:使用者名稱、帳號、座位不能被兩個並行請求同時搶到。
- 跨通道依賴:訊息佇列通知「圖片已處理」,消費端去讀資料庫卻讀到舊值——因為兩條通道速度不同。
非線性一致的危險場景(唯一性約束):
請求A 與 請求B 同時註冊 "alice"
兩者都讀到「alice 不存在」→ 都寫入成功 → 兩個 alice
線性一致性會強制兩者排出先後,後者必然看到前者,於是失敗CAP 定理的正確表述#
線性一致性與可用性在分區下不可兼得,這正是 CAP 的精確含義:
CAP 不是「三選二」。P(網路分區)是分散式系統無法迴避的前提,因此真正的選擇只發生在分區期間:要嘛保持線性一致而拒絕服務分區少數側(CP),要嘛維持可用而放棄線性一致(AP)。沒分區時,C 與 A 可以同時擁有。
詳見 CAP 定理。
線性一致性的效能代價#
維持線性一致需要節點間同步協調(共識、quorum 讀寫、鎖),這在每次操作上都加了一輪跨節點往返:
- 延遲:與最慢的多數派往返綁定,廣域部署下尤其明顯。
- 可用性:分區時少數側必須停止服務。
- 吞吐量:協調序列化了本可並行的操作。
經驗法則:只在「正確性真的需要」的環節(選主、唯一性、轉帳餘額)用線性一致,其餘環節用更弱的保證換效能。把強一致當預設值是常見的過度設計。
因果一致性 (Causal Consistency)#
因果一致性只保證有因果關係的操作順序,對並行操作不強加全序。它是「在分區下仍可用」的前提下,能拿到的最強一致性,因此特別有吸引力。
因果範例(留言串):
事件1: 使用者問「在嗎?」
事件2: 另一人答「在」 ← 因果依賴事件1
因果一致性保證:任何人都不會先看到「在」才看到「在嗎?」
但兩則互不相關的貼文,不同使用者可以看到不同先後(允許並行)判斷因果先後(happen-before)要靠 向量時鐘,而非實體時間戳。
複製延遲的三個坑#
採用非同步複製(讀從節點)時,使用者會撞見複製延遲造成的怪象。以下三種「會話保證 (session guarantees)」分別堵住一個坑:
| 異常 | 使用者感受 | 保證 |
|---|---|---|
| 讀不到自己剛寫的 | 改了暱稱重整卻看到舊暱稱 | 讀己之寫 (Read-Your-Writes) |
| 讀取倒退 | 刷新後留言「消失」(讀到更舊的副本) | 單調讀 (Monotonic Reads) |
| 看到結果卻沒看到原因 | 先看到回答、後看到問題 | 一致前綴讀 (Consistent Prefix Reads) |
flowchart TB
subgraph RYW["讀己之寫"]
W1[使用者寫入] --> R1["自己讀 → 必見自己的寫入"]
end
subgraph MR["單調讀"]
R2["第一次讀到新值"] --> R3["後續讀不會倒退到舊值"]
end
subgraph CP["一致前綴讀"]
E1["寫入有因果順序"] --> E2["讀取必依相同前綴順序"]
end
style RYW fill:#e3f2fd
style MR fill:#fff9c4
style CP fill:#c8e6c9實作手法:讀己之寫可把該使用者的讀取路由到主節點或記住其最後寫入時間戳;單調讀可把同一使用者固定綁到同一副本;一致前綴讀需保證有因果關係的寫入落在同一分片或按因果順序套用。
一致前綴讀為何在分片系統特別容易出錯
當資料分散在多個分片、各分片獨立複製時,不同分片的複製延遲不同。若「問題」與「回答」落在不同分片,觀察者可能先讀到延遲較小的分片(回答),再讀到延遲較大的分片(問題),於是看到「先有答案後有問題」的詭異前綴。
解法是讓有因果關係的寫入落入同一分片,或在系統層面追蹤因果依賴(向量時鐘)並按序套用。這也是為什麼很多系統用「對話 ID」「貼文串 ID」作為分片鍵。
一致性與業務匹配#
flowchart TD
Q1{涉及唯一性 / 選主 /<br/>金額正確性?}
Q1 -->|是| L[線性一致性]
Q1 -->|否| Q2{需保證因果<br/>(問答、留言)?}
Q2 -->|是| C[因果一致性]
Q2 -->|否| Q3{使用者會困惑於<br/>讀到舊值?}
Q3 -->|是| S[會話保證]
Q3 -->|否| E[最終一致性]
style L fill:#ffccbc
style C fill:#ffe0b2
style S fill:#fff9c4
style E fill:#c8e6c9小結#
- 一致性是光譜:愈強愈好推理,但延遲、可用性、吞吐量代價愈高。
- 線性一致性是選主、唯一性約束等正確性的前提,但每次操作都付出跨節點協調的代價。
- CAP 的正確讀法:分區是前提,選擇只在分區期間於一致與可用之間做。
- 因果一致性是分區下可用的最強保證;判斷因果要靠向量時鐘。
- 複製延遲三坑(讀己之寫、單調讀、一致前綴讀)多數應用只需會話保證即可解決,不必動用線性一致。