搜尋文件

輸入關鍵字,搜尋標題與正文。

    分散式系統文件 06/08

    複寫、確認與故障切換

    本文提要

    增加資料副本能提升讀取吞吐並具備容錯潛力,但多一份備份並不保證不會遺失已確認的寫入。 複寫何時向呼叫端確認、主節點故障時如何提升備援,以及舊主節點恢復後的狀態處置,共同決定了系統在真實故障下的行為邊界。

    18px · 寬鬆行距

    閱讀設定

    排版預覽 · 示範文字(可捲動)

    檔案與目錄

    Linux 以一棵從根目錄 / 開始的樹狀結構組織檔案。目錄用來整理檔案,也可以包含其他目錄。

    pwd
    ls -la /home
    18px

    16、18、20、22px;隨瀏覽器文字大小縮放。

    欄寬套用於背景文章;此處預覽字級與行距。手機正文自動配合螢幕。

    文件與本文目錄
    本主題文件

    增加資料副本能提升讀取吞吐並具備容錯潛力,但多一份備份並不保證不會遺失已確認的寫入。 複寫何時向呼叫端確認、主節點故障時如何提升備援,以及舊主節點恢復後的狀態處置,共同決定了系統在真實故障下的行為邊界。

    本文接續一致性模型與操作歷程,探討多副本拓樸在運作與故障切換時的資料遺失風險,為後續的 Quorum 與衝突修復 提供實體架構基礎。

    查證日期:2026-11-04。複寫拓樸與延遲異常參考分散式儲存經典架構;同步與備援機制參考 PostgreSQL 18 官方文件(Warm Standby 與 Synchronous Replication)。未執行實際資料庫或叢集切換實驗,文中狀態歷程均為概念推演。

    複寫拓樸的三種主要架構

    依據寫入入口的位置,分散式系統常見三種複寫架構:

    1. 單主複寫(Single-Leader / Primary-Backup):
      • 所有的寫入操作必須送至單一的主節點(Leader/Primary)。
      • 主節點將變更以日誌(WAL 或 binlog)形式發送給一個或多個從節點(Follower/Standby)。
      • 讀取操作可由主節點或從節點承接。
      • 優點是寫入擁有全域單一決定點,避免寫入衝突;缺點是主節點是寫入吞吐的上限與單點故障源。
    2. 多主複寫(Multi-Leader):
      • 允許跨資料中心或多個節點同時接受寫入。
      • 各主節點在本地完成寫入後,彼此非同步交換變更日誌。
      • 優點是容忍單一資料中心斷線且寫入延遲低;缺點是必須處理並行寫入帶來的衝突(如兩個資料中心同時修改同一個匯出工作的狀態)。
    3. 無主複寫(Leaderless):
      • 客戶端直接將寫入與讀取並行發送至多個節點,或由協調節點(Coordinator)代為分發。
      • 依賴多數派讀寫(Quorum)與版本向量解決分歧(將於下一篇詳述)。

    寫入確認的時序邊界

    在單主架構中,主節點何時向客戶端回覆「寫入成功」,是吞吐量與持久性之間的根本取捨:

    TEXT
    Client          Leader             Follower (Standby)
      |                |                       |
      |--- Write ----->|                       |
      |                |-- Append log (WAL)    |
      |                |                       |
      |                |--- Replicate log ---->|
      |                |                       |-- Append log
      |                |<-- Acknowledged ------|
      |<-- Success ----|                       |
    

    1. 完全非同步複寫(Asynchronous)

    主節點將資料寫入本地儲存後,立即回覆客戶端「成功」,稍後才透過背景程序將日誌送給從節點。

    • 優勢:寫入延遲極低,完全不受從節點效能或網路抖動影響。
    • 代價:若主節點在日誌送達從節點前崩潰,已經向客戶端承諾的寫入將從備援節點上憑空消失。

    2. 完全同步複寫(Synchronous)

    主節點必須等待所有從節點確認已將日誌寫入磁碟,才向客戶端回覆成功。

    • 優勢:保證任何一台從節點都擁有完整的最新資料。
    • 代價:只要叢集中有任何一台從節點暫停(例如 GC 或網路中斷),整個系統的寫入就會完全卡死;活性(Liveness)極脆弱。

    3. 半同步複寫(Semi-synchronous)

    主節點只等待固定數量的從節點(通常為至少一台)確認收到,即回覆成功。以 PostgreSQL 18 為例,其 synchronous_commit 提供多種等級:

    • off:不等待 WAL 刷盤即可回覆。
    • local:僅等待本地 WAL 刷盤。
    • remote_write:等待備援節點作業系統接收日誌(尚未落盤)。
    • on(預設):等待至少一個備援節點完成 WAL 磁碟寫入(flush)。
    • remote_apply:進一步等待備援節點完成資料庫交易重播(replay),以防止從 Standby 讀到陳舊資料。

    參考 PostgreSQL 18:Warm Standby / Streaming Replication。

    複寫延遲引發的讀取異常

    當讀取流量導向非同步或半同步從節點時,複寫延遲(Replication Lag)會打破客戶端的預期:

    1. 讀己所寫(Read-Your-Writes)違約:
      • 使用者在網頁發起「文章匯出」,寫入主節點並成功建立工作 ID job-101。
      • 瀏覽器立即跳轉至進度頁面,該請求被路由到從節點讀取。從節點尚未收到剛才的寫入,回報「找不到此工作 ID」,引發使用者恐慌與重複點擊。
    2. 單調讀(Monotonic Reads)違約:
      • 使用者讀取延遲較小的從節點 A,看到匯出進度為 50%。
      • 使用者按下重新整理,請求被路由至延遲較大的從節點 B,看到進度倒退為 20% 或 PENDING。

    常見緩解方案包括:追蹤使用者的更新時間戳,近期有寫入的使用者強制導向主節點讀取;或由客戶端攜帶版本編號,從節點若版本落後則拒絕或轉發。

    故障切換與裂腦(Split-Brain)

    當主節點停止回應時,系統必須執行容錯切換(Failover):

    1. 判定主節點失效(通常依賴心跳逾時)。
    2. 從現有的從節點中挑選資料最新的候選者。
    3. 將該候選節點提升(Promote)為新主節點。
    4. 重定向所有客戶端與從節點至新主節點。

    在這個過程中隱藏著兩個極度危險的邊界:

    1. 遺失寫入與外部副作用衝突

    假設原主節點向客戶端確認了「工作已完成(COMPLETED)」,但該日誌尚未抵達從節點,主節點突然斷電。 從節點被提升為新主節點後,其記錄中的工作依然是「執行中(RUNNING)」。 若外部系統(如通知服務或計費系統)已經依據前次成功的確認寄出信件或完成請款,新主節點可能指派另一個 worker 重複執行匯出,導致不可撤銷的重複副作用。

    2. 裂腦(Split-Brain)

    若原主節點並未真正當機,只是因為暫時性的全域網路中斷或長達數十秒的 Java 虛擬機器 GC 停頓,導致監控程式誤判其死亡並提升新主節點。 此時網路上存在兩台自認為是主節點的主機:

    • 接收到舊主節點請求的客戶端持續向它寫入;
    • 其他客戶端向新主節點寫入。

    當舊主節點恢復網路連線時,兩台機器的交易日誌已經沿著不同分支演進。如果不依賴分散式共識機制(如第三階段介紹的 Raft 或世代號 Fencing),資料庫將無法安全合併這些衝突數據,通常必須人工介入或無情捨棄一側的寫入。

    理解了單主架構在切換與同步時的脆弱性後,下一篇Quorum、版本與衝突修復將轉向無主架構,探討如何利用多數派數學交集與版本機制,在沒有單一主節點的前提下維持資料完整性。