具代表性的面試主題

如何設計 Saga 編排多服務訂單履約並處理補償?

系統設計困難
OfferCC 編輯團隊發佈 更新

題幹

一筆訂單需要依序完成庫存預留、付款授權、配送建立和優惠券扣減,但每個服務擁有獨立資料庫,不能使用兩階段提交。請設計 Saga,並說明編排、重試、補償、逾時、人工介入和狀態查詢。

1. 題目

使用者提交訂單後,系統要在庫存、付款、配送和優惠券四個服務中完成履約。任一步驟可能因業務規則或網路故障失敗,系統不能回滾已提交的遠端資料庫交易。請設計一套 Saga,讓訂單最終進入 CONFIRMED 或 CANCELLED,並解釋每個區域交易和補償動作。

2. 約束與釐清

  • 每個服務只對自己的資料庫執行本地 ACID 交易。
  • 流程允許最終一致,但不能無限占用庫存或付款授權。
  • 重試可能重複執行步驟;服務必須使用冪等鍵和狀態機保護。
  • 需要區分可重試的技術失敗、不可重試的業務拒絕,以及需要人工處理的補償失敗。

3. 核心思路

Saga 把一個長交易拆成一系列有序的區域交易 T1 ... Tn。每個步驟成功後記錄進度並觸發下一步;若後續步驟失敗,則反向執行已完成步驟的補償 Ck ... C1。補償不是資料庫回滾,而是新的業務操作,例如釋放庫存、撤銷付款授權、取消配送和返還優惠券。

編排式 Saga 由持久化協調器保存狀態和下一個動作,適合需要明確流程、逾時和人工介入的履約鏈路。事件驅動的 choreography 可以減少中心協調,但流程可見性和迴圈控制更難。無論模式如何,步驟和補償都應帶有 saga_id、step_id、版本和冪等鍵。

4. 參考實作

text
start(order):
  saga = create_saga(order.id, state="RESERVE_STOCK")
  dispatch(saga, "ReserveStock")

on_step_result(saga_id, step_id, result):
  saga = load_and_lock(saga_id)
  require result.version == saga.version + 1
  if result.success:
    saga.completed_steps.append(step_id)
    saga.version += 1
    next = next_step(saga)
    persist(saga)
    dispatch(next) if next else finish_confirmed(saga)
  else if result.business_rejection:
    saga.state = "COMPENSATING"
    persist(saga)
    dispatch(compensation_for_last_completed(saga))
  else:
    schedule_retry_or_timeout(saga, step_id)

on_compensation_result(saga_id, step_id, result):
  record_attempt(saga_id, step_id, result)
  if result.success:
    dispatch(previous_compensation(saga))
  else:
    mark_manual_intervention(saga, reason=result.error)

5. 一致性與正確性

協調器狀態必須持久化,否則程序崩潰會遺失下一步。每個命令和事件都使用冪等鍵;消費者先記錄已處理的 step_id,再提交業務變更,避免重試造成重複扣庫存。步驟完成與發布下一個命令之間也有發送失敗窗口,應使用 Outbox、可靠佇列或 CDC 保證命令最終可見。

補償順序通常與成功步驟相反,但不要求每個動作都能嚴格撤銷;有些動作只能做業務上的反向調整,例如退款而非撤回已發出的通知。狀態查詢應回傳目前步驟、已完成步驟、重試次數和人工原因,避免把「處理中」誤報為成功。

6. 追問與陷阱

  • 不要把 Saga 描述成跨資料庫的原子回滾;它提供的是可復原的最終一致性。
  • 補償動作也會失敗,必須有重試、死信、告警和人工接管,而不是無限自動迴圈。
  • 只靠全域分散式鎖會放大故障域,無法解決遠端服務已經提交後的業務撤銷。
  • 需要明確庫存預留和付款授權的 TTL,逾時後由定時任務或事件驅動釋放資源。

7. 延伸閱讀

可以比較編排式與 choreography:前者集中記錄狀態、順序和逾時,後者透過事件解耦服務但更難追蹤全鏈路。也應討論「不可補償的副作用」、業務語意鎖、版本衝突、稽核日誌,以及何時把流程改回單體資料庫交易。

8. 面試評分點

能拆分區域交易

應明確列出庫存、付款、配送和優惠券步驟,說明每個服務只提交自己的本地交易。

能設計補償狀態機

應展示成功路徑、失敗路徑和反向補償,並區分業務拒絕、技術重試和人工接管。

能處理冪等與訊息可靠性

應使用 saga_id、step_id、冪等鍵和 Outbox/可靠佇列,涵蓋協調器崩潰與命令發送窗口。

能說明業務邊界

應承認補償不是回滾,討論 TTL、不可補償副作用、狀態查詢和最終一致性的使用者體驗。

公開來源

同類題目

資源

產品比較

每份比較均依官方資料整理,並標示複核日期。

查看完整比較

相關面試工具

用 Solve 整理系統設計回答

從澄清需求開始,展開規模、架構、元件選擇和取捨。

查看工具