資料複寫 (Replication)

一句話定位

Replication 是「把同一份資料的副本放多台機器」,目的有三:低延遲、高可用、讀取擴展
真正的挑戰不是複製,是當資料持續更新時,副本要如何保持一致

三大架構一覽

架構 寫入端 優點 缺點 適用
Single-Leader 只有 primary 接寫 簡單、無寫入衝突 Leader 是 SPOF 絕大多數讀取密集應用
Multi-Leader 多 leader 都能寫 跨 DC 寫入快、容忍 DC 失效 寫入衝突難解 多資料中心、離線編輯
Leaderless 任何 replica 都能寫 高可用寫入 一致性弱、需應用層合併 高可用 + 最終一致可接受

1. Single-Leader Replication

基本運作

採用者:PostgreSQL(9.0+)、MySQL、MongoDB、Kafka、RabbitMQ

同步 vs 非同步(核心取捨)

模式 寫入路徑 一致性 可用性
同步 Leader 等 follower 確認才回 client ✅ Follower 保證有最新 ❌ Follower 沒回應就卡住
非同步 Leader 發完就回 client ❌ Leader 掛了可能丟資料 ✅ 快、不卡住
半同步(Semi-sync) 一台同步、其他非同步 折衷:至少 2 份保有最新 折衷
面試考點

「同步 vs 非同步沒有誰更好,看你的系統對一致性 vs 可用性的要求」—— 這種講法比硬說某個更好有深度。

Failover(Leader 掛掉)

1. 偵測失效     ← 通常用 timeout(如 30 秒沒心跳)
2. 選出新 leader ← 投票 / controller 指定,最好選資料最新的
3. 重新設定     ← Client 寫入導向新 leader;舊 leader 回來時變 follower

Failover 的地雷

Replication Log 的四種實作

方式 做法 缺點
Statement-based 直接傳 SQL 語句 NOW() / RAND() 等非確定函數會不一致
WAL Shipping 直接傳預寫日誌 Storage engine 緊耦合,版本升級可能要停機(PostgreSQL/Oracle)
Logical(row-based) Row 層次變更 + 自有格式 解耦 storage engine,外部系統可解析 → CDC 基礎(MySQL binlog)
Trigger-based 用 trigger / stored procedure Overhead 高,但客製靈活、可跨資料庫

Replication Lag 三大不一致問題

非同步 replication 下,從 follower 讀可能看到過時資料(最終一致性 / eventual consistency)。

問題 1:Read-After-Write(讀自己剛寫的)不一致

用戶發貼文 → 馬上看 → 看不到(讀到還沒同步的 follower)

解法

  1. 讀自己改過的東西時從 leader 讀:「自己的 profile 永遠從 leader 讀」
  2. 追蹤最後寫入位置(LSN / binlog position):client 帶上 LSN,replica 必須追上才能服務
  3. 短暫讀 primary 視窗:寫入後 N 秒內全部讀 primary
Primary:   [LSN 1230][1231][1232][1233][1234]
Replica A: [LSN 1230][1231][1232]              ← 還沒追上
Replica B: [LSN 1230][1231][1232][1233][1234]  ← 已追上
Client 帶 LSN 1234 → 路由到 Replica B 或 Primary
為什麼用 LSN 而不是時鐘

牆上時鐘有 clock skew,log position 沒有這問題。

問題 2:Monotonic Reads(單調讀取)不一致

用戶讀到朋友的留言 → 刷新被路由到 lag 更大的 follower → 留言消失(時光倒流)

解法同一用戶總是讀同一台 replica(如用 hash(user_id) 路由),那台掛了才換。

問題 3:Consistent Prefix Reads(因果一致性)不一致

A 問問題、B 回答 → 觀察者因不同 replica lag 先看到 B 回答才看到 A 問題(因果顛倒)

解法:把有因果關係的寫入放同一 partition;或用 version vector 記錄因果依賴。


2. Multi-Leader Replication

單一 DC 內幾乎不值得

複雜度遠超過好處。跨多個 DC 的場景才有意義

適用場景

場景 為什麼
多 DC 部署 每 DC 自己有 leader,跨 DC 非同步複寫,DC 失效可獨立運作
離線編輯 手機行事曆 App 無網路時繼續編輯,連網再同步
協作編輯 Google Docs:每用戶的本地副本是一個 leader

Multi-Leader 最大挑戰:寫入衝突

用戶 A 和 B 同時在不同 leader 改同一筆,都成功 → 非同步複寫時衝突。

衝突迴避(最簡單)

確保特定記錄的所有寫入走同一個 leader(如同一用戶永遠路由到同一 DC)。DC 掛了就失效。

收斂到一致狀態(Converging)

方法 怎麼做 代價
Last Write Wins(LWW) 比 timestamp,最大者勝 會丟資料(Cassandra 唯一支援)
Replica 優先序 編號大的 replica 優先 一樣丟資料
合併值 按字母順序串接 業務語義不見得對
記錄衝突、稍後解決 保留所有版本 需要應用程式或用戶決定

自訂衝突解法

自動衝突解法(研究方向)

Replication Topology

Topology 形狀 缺點
Circular 環狀(MySQL 預設) 任一節點掛 → replication 中斷
Star 一個中心節點轉發 中心節點是 SPOF
All-to-all 全互連 容錯好,但寫入到達不同 replica 順序可能不一致(需 version vector)

3. Leaderless Replication

源自 Amazon Dynamo,任何 replica 都能直接接受寫入。代表系統:Riak、Cassandra、Voldemort。

基本運作

n=3 個 replica,其中一台暫時不可用:
  Client 同時寫 3 台
  → 2 台成功就算寫入成功(w=2)
  → 忽略沒回應那台

問題:那台 replica 復原後資料過時

解法(讀取時補救):

機制 做什麼
Read Repair(讀取修復) 並行讀多台,發現舊版本就把新值寫回去(頻繁讀的 key 很有效)
Anti-entropy Process(反熵) 背景持續掃描差異補資料(沒順序保證、可能有延遲)

Quorum 讀寫

設總副本 n、寫入確認 w、讀取查 r

Quorum 條件

w + r > n → 讀寫節點必然有重疊,讀取至少有一台有最新資料

配置 容忍失效節點
n=3, w=2, r=2 1 個
n=5, w=3, r=3 2 個
n=3, w=3, r=1 0 個(讀超快、寫卡住)
n=3, w=1, r=3 0 個(寫超快、讀貴)

Quorum 的限制(仍可能讀到舊值)

Dynamo-style 主要針對能接受最終一致性的場景優化。需要 read-after-write / monotonic reads / consistent prefix 要靠 transaction 或 consensus。

Sloppy Quorum & Hinted Handoff

網路中斷讓 client 連不到「家節點」但連得到其他節點:

選項 行為
嚴格 quorum 返回錯誤(CP)
Sloppy quorum 接受寫入,暫存到「現在連得到」的節點(不在原本 n 台裡)→ AP

Hinted Handoff:網路恢復後把這些臨時保管的寫入送回家節點

「就像沒帶鑰匙借住鄰居,拿到鑰匙後再搬回自己家。」

偵測並發寫入:Version Vector

每個 replica 為每個 key 維護自己的 version number 集合 → version vector(並非 vector clock,但概念相近)。

Server 為每個 key 維護 version;每次寫入遞增
Client 讀取 → server 回傳所有未被覆蓋的值 + 最新 version
Client 寫入 → 帶上前次讀的 version + 合併前次讀到的所有值
Server 收到帶 version 的寫入 → 覆蓋 ≤ 該版本的值,保留更高版本(並發)

Happens-before 關係:如果 B 知道 A 存在或建立在 A 之上 → A 先於 B。若兩操作互不知情 → 並發(concurrent)


系統設計面試怎麼談 Replication

主動帶進議題

場景 主動說
讀取密集 「加 read replica 分散讀取,需要討論 replication lag」
高可用 「Primary 是 SPOF,設計 automatic failover」
地理分散 「每 DC 一個 leader,multi-leader 跨 DC 非同步複寫」
高可用 + 寫入擴展 「Cassandra quorum 寫入(w=2, n=3)」

常見面試題型一句話對應

題型 預設架構
社群媒體(Twitter/IG) Single-leader + read replica,自己的內容從 primary 讀
金融(支付/庫存) Single-leader + 半同步,寧停機不要不一致
全球 CDN / cache Multi-leader + LWW(資料遺失可接受)+ TTL
IoT / metrics Leaderless(Cassandra),高寫入、LWW 可接受
協作 App(Docs 類) Multi-leader + CRDT / OT

高頻 Deep-Dive 問題

「Read-After-Write 怎麼保證?」

  1. 自己的資料從 primary 讀
  2. 追蹤 LSN / binlog position(不要用牆上時鐘 —— clock skew)
  3. 短暫讀 primary 視窗

「Failover 怎麼避免資料遺失?」

  1. 半同步 replication(至少一台 follower 同步)
  2. 資料最新的 follower 為新 primary
  3. 強一致要求高 → Raft / Paxos consensus(PostgreSQL 的 Patroni、etcd)

「Multi-leader 衝突怎麼處理?」

  1. 先問衝突有多嚴重(金融不能丟、社群 profile LWW 可接受)
  2. 衝突迴避(路由到同一 leader)
  3. 保留所有版本 + 應用層合併(購物車類)
  4. CRDT(計數器、集合、有序列表)

「Quorum + 網路分區怎麼辦?」

面試話術範例

「我會用 leader-follower。寫入走 primary,3 台 replica 分散讀取。非同步 replication 有幾秒 lag,多數讀取可接受;但用戶剛發的貼文要馬上看到,我在 session 裡追蹤最後寫入的 LSN,那段時間強制從 primary 讀。」

「為了高可用設計 automatic failover:每個 follower 監控 replication lag,primary 超過 30 秒沒心跳就把 lag 最小的 follower 提升為新 primary。注意 split brain 風險,用 fencing token 確保舊 primary 完全下線才讓新的接管。」