當多個微服務必須協作完成一個業務流程,怎麼避免狀態不一致?怎麼處理失敗回退?本章拆解:

  • 為什麼分散式交易(distributed transactions)通常不是答案
  • Saga 模式如何在沒有全域鎖的前提下協調多步驟流程

交易(Transactions)回顧#

交易把多個動作視為單一單位,要嘛全成功、要嘛全失敗。

ACID 四性質#

ACID = Atomicity、Consistency、Isolation、Durability

  • 原子性(Atomicity):所有操作要嘛全部完成,要嘛全部不發生
  • 一致性(Consistency):交易結束後資料庫處於有效狀態
  • 隔離性(Isolation):多個交易並行時互不干擾
  • 持久性(Durability):完成的交易不會因系統故障消失

關聯式資料庫普遍提供 ACID;MongoDB 4.0 之前只支援單一文件範圍的 ACID。

Figure 5.1:單一 ACID 交易內同時更新兩張表

微服務仍可使用 ACID,但範圍變小#

  • 單一微服務內部的資料庫操作仍可享有 ACID
  • 但跨服務操作就無法保證原子性

把原本一個 ACID 交易拆成多個服務各自的子交易後,整個流程的原子性就消失了。中途某一步失敗時,前面已 commit 的步驟不會自動回退。

Figure 5.2:Invoice 與 Order 在兩個獨立交易中變更

兩階段提交(2PC):為什麼不該用#

2PC 嘗試用兩階段協調分散式交易:

  • 投票階段(Voting Phase):協調者問所有 worker 是否能執行各自的變更;任何一個說 NO 就整體中止
  • 提交階段(Commit Phase):所有 worker 都同意後,協調者通知大家正式執行

Figure 5.3:2PC 投票階段,worker 表決能否執行本地變更

Figure 5.4:2PC 提交階段,變更實際套用

問題#

  • 隱性鎖:worker 投票同意後,必須鎖住資源確保稍後仍能 commit;管理本機鎖已經夠難了,更何況跨節點
  • 不再具備 isolation:commit 訊息到達各 worker 的時間不同,外界可能短暫看到部分節點已變更、部分尚未變更
  • 延遲與失敗模式:worker 投票後沒回應 commit,怎麼辦?多種失敗模式必須人工介入
  • 規模放大會放大問題

Pat Helland:「在多數分散式交易系統中,單一節點失敗會讓整個交易卡住。系統越大,越容易整體掛掉。如同要求飛機所有引擎全運作的設計——多裝一具引擎反而降低可靠性。」

結論:避開分散式交易。除非交易期極短、參與者極少。

不拆會怎樣?#

最簡單的選項是:不要把這份資料拆開

  • 若某些狀態必須以原子、一致的方式管理,就把它與相關功能一起留在單一服務(或單體)裡
  • 還在拆解單體的早期階段,跨交易的部分先不動,等其他地方拆完累積經驗再回頭處理

Saga:跨服務工作流的正解#

起源#

Saga 概念來自 Hector Garcia-Molina 與 Kenneth Salem 1987 年論文,原本要解決的是長活交易(Long-Lived Transactions, LLT)——一個交易跨越數分鐘、數小時甚至數天,期間鎖住的資源會嚴重影響其他作業。

論文建議:把 LLT 拆成多個短交易,每個短交易自身具備 ACID,但整體 saga 不再追求單一原子性。

Saga 與微服務#

把業務流程拆成多步驟,每步交給不同微服務處理。每一步在自己的資料庫內可享 ACID,但 saga 整體無法 rollback。

Saga 不提供 ACID 的原子性,它提供「足以推理當前狀態」的資訊,讓你決定接下來怎麼處置。

MusicCorp 訂單履行範例#

下單 → 收款 → 給點數 → 倉庫保留庫存 → 包裝 → 出貨

每步呼叫對應的微服務,內部各自 ACID。

Figure 5.5:以 saga 模型化的訂單履行流程範例

Saga 的失敗模式#

兩種復原策略:

  • 向後復原(Backward Recovery):撤銷已完成步驟,需定義補償動作(compensating action)
  • 向前復原(Forward Recovery):從失敗處重試,需保留足夠資訊以續跑

兩者可混用,依該步驟的本質決定。

Figure 5.6:嘗試包裝商品時,倉庫卻找不到該商品

Saga Rollback:補償交易(Compensating Transaction)#

ACID 的 rollback 是「在 commit 前取消」,但 saga 中早已 commit 的步驟需要新的補償交易來抵銷。

補償交易是語意性回退(Semantic Rollback),無法把世界恢復到原狀。

例:如果你已經寄了「訂單出貨中」的 email,補償交易能做的不是「取消那封 email」,而是「再寄一封說明訂單已取消」。

回退不代表資料消失——你可能還是希望保留這筆失敗訂單的紀錄供分析。

Figure 5.7:透過補償交易觸發整個 saga 的 rollback

重新排序步驟以減少回退#

把容易失敗的步驟提前、容易補償的步驟在前;不容易補償的步驟放後面。

例:把「給點數」從「收款後」移到「真正出貨後」——這樣若包裝失敗就不必回退點數。

Figure 5.8:將步驟移到 saga 後段以減少失敗時需 rollback 的範圍

同時混用向前與向後復原#

訂單流程後段(已收款且包裝完)若出貨失敗,整單回退反而怪——較合理的做法是重試出貨,或交由人工介入。

實作 Saga 的兩種風格#

1. 編排式(Orchestrated Saga)#

中央協調者(Orchestrator)控制整個流程。

  • 指揮控制式(command-and-control)
  • 大量使用請求/回應呼叫
  • 業務流程集中、可見、易於理解

優點:

  • 業務流程在一處明確展現,新人易上手
  • 整體進度與狀態追蹤容易

缺點:

  • 領域耦合提升:協調者必須認識所有下游服務
  • 邏輯容易被吸進協調者,下游服務變得「貧血」(anemic)

邏輯只要有地方可以集中,就一定會被集中。」(If logic has a place where it can be centralized, it will become centralized!)

緩解方式:用多個小型協調者各自管自己的流程(Order ProcessorReturnsGoods Receiving 各管一段),都共用 Warehouse 服務。

Figure 5.9:以編排式 saga 實作訂單履行流程的範例

BPM 工具(Business Process Modeling)通常打著「讓非開發者畫流程」的旗號,但實務上幾乎都是開發者在用,且帶來版本控制差、難測試等問題。

想嘗試的話,作者建議看 Camunda、Zeebe——它們較貼近開發者工作流。

2. 編舞式(Choreographed Saga)#

責任分散到所有參與服務,靠事件驅動協作

  • 信任但驗證(trust-but-verify)
  • 服務各自監聽自己關心的事件,做完後再發出事件
  • 沒有中央協調者

Figure 5.10:以編舞式 saga 實作訂單履行流程的範例

優點:

  • 領域耦合最低:每個服務只需知道「收到 X 事件要做 Y」
  • 不會發生邏輯集中現象

缺點:

  • 追蹤狀態困難:流程不在任何單一處顯式呈現
  • 想知道某個 saga 走到哪一步、哪些補償該執行,沒有明顯入口

解法:為每個 saga 配一個關聯 ID(Correlation ID),跟著所有事件流動。再做一個專門的服務 vacuum 所有事件,就能投影出每筆 saga 的當前狀態。

Correlation ID 在編舞式 saga 中幾乎是必備

混合風格#

兩種風格不互斥。例如外層採用編舞式,到了 Warehouse 內部處理「揀貨 → 包裝 → 派送」的子流程時改採編排式。

不論選哪種風格,都要有一致的方式讓你能追蹤一筆 saga 走到哪、哪裡卡住。否則錯誤排查與復原會變得非常痛苦。

編舞式 vs 編排式:怎麼選?#

情境推薦風格
單一團隊負責整個 saga編排式(管理較簡單)
多團隊共同參與 saga編舞式(解耦讓各團隊獨立工作)

作者個人偏好編舞式——額外的追蹤複雜度通常被鬆耦合的好處抵銷。

Saga vs 分散式交易#

  • 分散式交易(如 2PC)將失敗風險疊加:規模放大反而降低可用性
  • Saga 顯式建模業務流程:流程本身成為一等公民,新進開發者更容易理解

推薦延伸閱讀:

  • 第一版《Building Microservices》第 4 章
  • 《Enterprise Integration Patterns》(雖未直接提 saga,但深入討論編排與編舞)
  • Pat Helland《Life Beyond Distributed Transactions》(acmqueue)

小結#

  • 分散式交易(特別是 2PC)在大多數場景下不該採用
  • 跨服務的業務流程應顯式建模為 saga
  • Saga 不提供 ACID 原子性,但提供「足以推理狀態並決定如何收拾」的資訊
  • 補償交易是語意性回退,不是時光倒流
  • 編排式 = 集中、可見、耦合稍高;編舞式 = 分散、鬆耦合、追蹤難
  • Correlation ID 是編舞式 saga 的命脈
  • 業務流程值得作為一等公民被認真設計