總覽與定位
從「資料有多份」走向「讀者能看到什麼保證」;這是複寫系統真正困難的地方。
基礎複寫回答的是「資料怎麼被複製到多個節點」;進階複寫一致性要回答的是:在網路延遲、節點故障、併發寫入與 Failover 之下,Client 到底能相信自己讀到什麼?
這個問題不能只用「同步」或「異步」二分。實務上你會同時調整複寫拓撲、Quorum 參數、讀取路由、版本追蹤、衝突解決與 Session Guarantee,最後得到一組符合產品需求的保證。
複寫基礎篇已介紹 Single-Leader / Multi-Leader / Leaderless、同步與異步、以及 Replication Lag 異常。這篇會把焦點放在「如何明確宣告並實作一致性保證」,尤其是 Dynamo-style 與跨區資料庫常見的設計取捨。
一致性保證光譜
面試與架構設計最常混淆的點:Linearizability、Serializability、Eventual Consistency 不是同一個維度。
討論複寫一致性時,第一步是把名詞放在正確座標上。Linearizability 關心「單一物件的即時可見性」;Serializability 關心「多筆交易的隔離順序」;Eventual Consistency 則只承諾沒有新寫入後,副本最後會收斂。
| 保證 | 核心語意 | 常見實作 | 代價 |
|---|---|---|---|
| Linearizability | 寫入成功後,所有後續讀取都必須看到該寫入或更新版本。 | Leader 強路由、Consensus、同步 Quorum。 | 跨區 Latency 高;Partition 時偏向拒絕服務。 |
| Sequential Consistency | 所有 Client 看到同一個順序,但不要求符合真實時間。 | 單序列日誌、單主複寫。 | 需要集中排序點,吞吐受限於排序瓶頸。 |
| Session Guarantees | 至少對同一個使用者維持合理體驗,如 Read-your-writes。 | Sticky Routing、Session Token、Leader fallback。 | 需要追蹤 session 進度;負載均衡彈性下降。 |
| Eventual Consistency | 若停止寫入,所有副本最後會趨向相同狀態。 | Async Replication、Anti-entropy、CRDT。 | 短時間可讀到舊值或衝突值,應用層要能處理。 |
四種保證大致由強到弱排列。該看「代價」欄:越往上,代價越集中在跨區延遲與分區時的可用性;越往下,處理舊值與衝突的責任就越往應用層推。
Serializability 是交易隔離等級,保證多個交易的結果等價於某個序列執行;Linearizability 才是在描述操作完成後的即時可見性。資料庫可以 Serializable 但不是 Linearizable,尤其在多副本、異步讀取與快照讀場景中。
Session Guarantees:用戶體驗的一致性底線
很多產品不需要全域強一致,但幾乎都需要「同一個使用者不要被系統背叛」。
在社群、電商、SaaS 後台與內容平台中,對所有使用者提供 Linearizability 可能太昂貴;但如果使用者剛更新個人資料,刷新頁面卻看不到,體驗會直接崩壞。Session Guarantees 是在最終一致架構上加的一組局部保證。
同一個使用者寫入成功後,自己的後續讀取必須看到該寫入。常用做法是寫入後短時間強制讀 Leader,或把最新版本 token 帶回 Client。
同一使用者看過版本 8,就不能下一次讀到版本 6。可透過 Sticky Routing、最小版本要求或 session token 達成。
同一使用者的寫入必須依序套用,避免「先改密碼、再改信箱」被副本反序接收。常見策略是每個 session 綁定單一寫入序列點。
使用者基於某次讀取做出的後續寫入,不能被套用到還沒看過該讀取版本的副本上,否則會破壞因果關係。
四條保證各自獨立、可以分開採用:上兩條(RYW、Monotonic Reads)管「讀到什麼」,下兩條(Monotonic Writes、Writes-follow-reads)管「寫入的順序與因果」。該看的是每一列右側的實作手段——它們幾乎都收斂到 Sticky Routing 與版本 token 這兩招,這正是緊接著的 Session Token 模式要展開的東西。
Session Token 的實務模式
每次讀寫回應都帶上副本進度,例如 {region: "ap", log: 1842} 或 per-shard vector。Client 後續請求帶回 token,Gateway 只把請求路由到進度至少達到該 token 的副本;若沒有副本符合,就等待、回 Leader 或回應可重試。
例如「訂單付款成功後,使用者自己的訂單頁必須立刻看到已付款」是 Read-your-writes;但「所有客服後台立即看到已付款」可能是另一個 SLA。把保證寫到 user journey 上,才知道哪些讀取要付出強一致成本。
Quorum 讀寫:用交集換取可調一致性
Leaderless 複寫的核心公式:只要讀寫集合有交集,就有機會讀到最新版本。
在 Dynamo-style 系統中,一筆資料通常有 N 個副本。寫入成功需要 W 個副本確認,讀取則向 R 個副本查詢。若滿足:
W + R > N
理論上讀集合與寫集合必定有交集,讀取方就能遇到至少一個最新版本;再透過版本比較選出最新值,並把舊副本補上。
| 設定 | 語意 | 適合場景 | 風險 |
|---|---|---|---|
N=3, W=1, R=1 |
最快但沒有交集保證。 | 可容忍舊值的計數、推薦、快取資料。 | Stale Read 常見,衝突收斂依賴背景修復。 |
N=3, W=2, R=2 |
典型 Majority Quorum。 | 一般 metadata、購物車、低額風險狀態。 | 節點故障時可用性下降;尾延遲受慢副本影響。 |
N=5, W=3, R=3 |
跨多 AZ 的強交集設定。 | 跨區重要狀態、需要較高讀寫信心的資料。 | 跨區 RTT 會放大 P99 latency。 |
N=5, W=1, R=5 |
寫入快、讀取嚴格。 | 寫多讀少,且讀取時願意付成本校驗。 | 任何副本慢都拖累讀取。 |
只有第一列 W+R=2 不大於 N=3、沒有交集保證,其餘三列都滿足 W+R>N。同樣有交集時,W 與 R 的分配決定成本落在寫入還是讀取——最後一列把成本全押在讀取端。
W + R > N 只保證讀寫集合有交集;若網路分區、 sloppy quorum、時鐘版本錯誤、或讀取沒有比較版本並修復舊副本,仍可能讀到舊值。Quorum 是工具,不是魔法。
W + R > N 讓讀寫集合相交,並透過版本比較找出新值;提高 R/W 能提升一致性信心,但會犧牲 latency 與故障時可用性。
互動演示:Quorum 與 Read Repair 實驗室
切換不同 R/W 設定,觀察 Stale Read、交集保證與 Read Repair 如何影響副本狀態。
下方模擬器使用 5 個副本。你可以選擇「快速最終一致」、「平衡 Majority」、「讀取嚴格」或「故障壓力」四種模式,依序執行寫入、讀取與 Read Repair,觀察哪些節點拿到新版本、讀取是否有機會遇到新值。
觀察重點:低 W/R 帶來低延遲但容易讀到舊值;Majority 需要更多 ACK,卻能透過交集與 Read Repair 快速收斂。
Version Vector 與衝突解決
當系統允許多點寫入,就必須知道兩個版本是「先後」還是「併發」。
在 Multi-Leader 或 Leaderless 架構中,兩個 Client 可能在不同區域同時修改同一筆資料。若只用單一 timestamp,會把併發修改粗暴壓成「最後寫入者勝」(LWW),看似簡單,實際上可能遺失使用者更新。
{A:2, B:1}
{A:1, B:2}
concurrent conflict
如何判斷版本關係
- A dominates B:A 的每個 counter 都大於或等於 B,且至少一個更大;代表 A 已包含 B 的歷史。
- B dominates A:反過來,B 是較新的線性後繼版本。
- Concurrent:兩者各有某個 counter 比對方大;代表併發寫入,不能安全自動覆蓋。
衝突解決策略
| 策略 | 適合資料 | 優點 | 風險 |
|---|---|---|---|
| Last-Write-Wins | 狀態覆蓋型欄位,如暱稱、最後登入時間。 | 簡單、儲存成本低。 | 時鐘漂移與併發寫入會造成資料遺失。 |
| Application Merge | 購物車、文件協作、設定集合。 | 保留業務語意,能做精準合併。 | 需要應用層處理 siblings 與冪等合併。 |
| CRDT | Counter、Set、Presence、協作狀態。 | 數學上可交換、可結合、可冪等,天然收斂。 | 資料模型受限制,刪除與壓縮需要設計。 |
由上往下,保留的業務語意越多,實作成本也越高。該看「風險」欄:LWW 的風險是資料遺失,另外兩種的風險是設計成本,這正是下方陷阱要提醒的取捨。
如果兩個裝置同時把購物車加入不同商品,LWW 會直接丟掉其中一邊。除非產品語意允許覆蓋,否則應優先使用 Application Merge 或 CRDT。
面試考點與架構決策
把一致性需求翻譯成可落地的讀寫路由、Quorum 與衝突策略。
回答框架:先問資料語意,再選保證
面試官問「如何設計跨區資料庫的一致性」時,不要一開始就背 CAP。先把資料分級:哪些資料必須強一致、哪些只需要 Session Guarantee、哪些可接受最終一致。接著為每一類資料選讀寫路由與修復策略。
| 資料類型 | 建議保證 | 設計手段 | 取捨說法 |
|---|---|---|---|
| 付款狀態 / 庫存扣減 | Linearizable 或 Consensus-backed。 | 單寫入 Leader、Raft/Paxos、Fencing Token。 | 願意犧牲跨區寫入延遲,換取不超賣、不重複扣款。 |
| 個人 Profile / 訂單頁 | Read-your-writes + Monotonic Reads。 | Session Token、Leader fallback、Sticky Routing。 | 保護使用者自己的體驗,不要求所有人即時看到。 |
| Feed、按讚數、推薦特徵 | Eventual Consistency。 | Async Replication、Anti-entropy、CRDT Counter。 | 允許短暫舊值,換取高吞吐與低延遲。 |
| 協作文件 / 購物車 | Causal Consistency + Merge。 | Version Vector、Application Merge、CRDT Set。 | 避免併發更新互相覆蓋,成本是資料模型與合併邏輯變複雜。 |
每一列都是一次「資料語意 → 建議保證 → 設計手段」的翻譯。該看「設計手段」欄:同一個系統裡,四類資料各走不同的讀寫路由,而不是全系統只選一種一致性。
標準答法是「資料分級 + 多模式讀寫」。關鍵狀態集中到 Consensus 或單 Leader;使用者體驗靠 Session Token;大量非關鍵讀寫採 Eventual Consistency;背景用 Read Repair / Anti-entropy 收斂。這比宣稱整個系統都強一致更可信。
若上游資料可能短暫舊值,下游快取、搜尋索引、通知系統與分析 pipeline 都必須知道它們收到的是 eventually consistent event,而不是絕對最新狀態。否則會在 audit、客服後台或風控判斷中放大不一致。