重試模式:建構具韌性的應用程式

  1. 情境與問題
  2. 解決方案
  3. 重試策略
  4. 實作考量
  5. 測試與驗證
  6. 何時使用此模式
  7. 與斷路器結合
  8. 相關模式
  9. 參考資料

當你的應用程式與遠端服務通訊時——資料庫、API、訊息佇列——可能會出錯。網路中斷、伺服器忙碌或暫時性逾時都可能導致請求失敗。重試模式幫助你的應用程式優雅地處理這些暫時性故障,將潛在的失敗轉化為成功。

情境與問題

分散式系統經常面臨暫時性故障:

  • 網路連線中斷:元件之間的短暫斷線
  • 服務不可用:部署或重啟期間的暫時性服務中斷
  • 逾時:服務在高負載下回應時間過長
  • 節流:服務在過載時拒絕請求

這些故障通常會自我修正。暫時過載的資料庫可能現在拒絕你的連線,但在清除積壓工作後一秒鐘就會接受。如果沒有重試機制,你的應用程式會將這些暫時性問題視為永久性故障,不必要地降低使用者體驗。

解決方案

設計你的應用程式以預期暫時性故障並透明地處理它們。重試模式引入一種機制,自動重試失敗的操作,將對業務功能的影響降到最低。

graph LR A["應用程式"] --> B["重試邏輯"] B --> C["遠端服務"] C -->|"成功"| D["回傳結果"] C -->|"暫時性故障"| E["等待並重試"] E --> C C -->|"超過最大重試次數"| F["處理例外"] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#ffe1e1 style D fill:#d3f9d8

💡 內建重試機制

許多現代客戶端函式庫和框架都包含可設定的重試邏輯。在實作自訂重試程式碼之前,請先查看你的函式庫文件。

!!!

重試策略

根據故障類型和應用程式需求選擇重試策略:

1. 取消

何時使用:故障表示永久性問題或即使重試也不會成功的操作。

範例

  • 驗證失敗
  • 無效的請求參數
  • 找不到資源錯誤

動作:立即取消操作並回報例外。

2. 立即重試

何時使用:故障不尋常或罕見,例如網路封包損毀。

範例

  • 隨機網路傳輸錯誤
  • 暫時性連線重設

動作:立即重試請求,不延遲。

3. 延遲後重試

何時使用:故障常見且與連線或服務負載相關。

範例

  • 連線逾時
  • 服務忙碌回應
  • 節流錯誤

動作:等待後再重試,使用以下延遲策略之一:

固定延遲:每次重試之間等待相同的時間。

嘗試 1 → 等待 2 秒 → 嘗試 2 → 等待 2 秒 → 嘗試 3

遞增延遲:線性增加等待時間。

嘗試 1 → 等待 2 秒 → 嘗試 2 → 等待 4 秒 → 嘗試 3 → 等待 6 秒 → 嘗試 4

指數退避:每次失敗後將等待時間加倍。

嘗試 1 → 等待 1 秒 → 嘗試 2 → 等待 2 秒 → 嘗試 3 → 等待 4 秒 → 嘗試 4 → 等待 8 秒 → 嘗試 5

帶抖動的指數退避:在指數延遲中加入隨機性,以防止多個客戶端同時重試(「驚群」問題)。

實作考量

記錄策略

適當記錄故障以避免警報疲勞:

  • 早期故障:記錄為資訊項目
  • 成功重試:記錄在除錯層級
  • 最終故障:僅在所有重試都用盡後記錄為錯誤

這種方法為操作人員提供可見性,而不會用自我修正問題的警報淹沒他們。

graph TB A["請求失敗"] --> B["記錄:INFO - 嘗試 1 失敗"] B --> C["等待並重試"] C --> D["請求再次失敗"] D --> E["記錄:INFO - 嘗試 2 失敗"] E --> F["等待並重試"] F --> G{"成功?"} G -->|"是"| H["記錄:DEBUG - 嘗試 3 成功"] G -->|"否(最大重試次數)"| I["記錄:ERROR - 所有重試已用盡"] style A fill:#ffe1e1 style H fill:#d3f9d8 style I fill:#ff6b6b

效能影響

調整重試策略以符合業務需求:

互動式應用程式(網頁應用程式、行動應用程式):

  • 快速失敗,較少重試次數
  • 嘗試之間使用短延遲
  • 顯示使用者友善訊息(「請稍後再試」)

批次應用程式(資料處理、ETL 作業):

  • 使用更多重試嘗試
  • 採用指數退避與較長延遲
  • 優先考慮完成而非速度

⚠️ 避免激進重試

激進的重試策略(許多重試且延遲最小)可能會使情況惡化:

  • 進一步降低已經過載的服務
  • 降低應用程式的回應性
  • 在系統中造成級聯故障

考慮在重試之外實作斷路器模式,以防止壓垮失敗的服務。

!!!

冪等性

在應用重試之前,確保操作是冪等的(多次執行是安全的)。非冪等操作可能導致意外的副作用:

問題情境

  1. 服務接收請求並成功處理
  2. 服務因網路問題無法傳送回應
  3. 客戶端重試,導致重複處理

解決方案

  • 設計操作為自然冪等
  • 使用唯一請求識別碼來偵測重複
  • 實作伺服器端去重邏輯

例外類型

不同的例外需要不同的重試策略:

例外類型 重試策略 範例
暫時性網路錯誤 延遲後重試 連線逾時、DNS 解析失敗
服務忙碌/節流 指數退避重試 HTTP 429、HTTP 503
驗證失敗 立即取消 無效憑證、過期權杖
無效請求 立即取消 HTTP 400、格式錯誤的資料
找不到資源 立即取消 HTTP 404

交易一致性

在交易中重試操作時:

  • 微調重試策略以最大化成功機率
  • 最小化回滾交易步驟的需求
  • 考慮分散式情境的補償交易
  • 確保重試邏輯不違反交易隔離層級

測試與驗證

🧪 測試檢查清單

  • 針對各種故障條件進行測試(逾時、連線錯誤、服務不可用)
  • 驗證正常和故障情境下的效能影響
  • 確認下游服務沒有過度負載
  • 檢查並行重試的競爭條件
  • 驗證不同故障階段的記錄輸出
  • 測試交易回滾情境

!!!

巢狀重試策略

避免分層多個重試策略:

問題:任務 A(有重試策略)呼叫任務 B(也有重試策略)。這會造成指數級的重試嘗試和不可預測的延遲。

解決方案:設定低層級任務快速失敗並回報故障。讓高層級任務根據自己的策略處理重試。

graph TB A["任務 A
(重試策略)"] --> B["任務 B
(無重試)"] B -->|"快速失敗"| A A -->|"根據策略重試"| B style A fill:#e1f5ff style B fill:#fff4e1

何時使用此模式

使用重試模式當

  • 你的應用程式與遠端服務或資源互動
  • 故障預期是暫時性且短暫的
  • 重複失敗的請求有很大機會成功
  • 操作是冪等的或可以變成冪等的

不要使用重試模式當

  • 故障可能是長期的(改用斷路器)
  • 處理非暫時性故障(業務邏輯錯誤、驗證失敗)
  • 解決可擴展性問題(改為擴展服務)
  • 操作有重大副作用且不是冪等的

與斷路器結合

重試和斷路器模式相輔相成:

  • 重試:透過再次嘗試操作來處理暫時性故障
  • 斷路器:當已知服務停機時防止重試
stateDiagram-v2 [*] --> Closed: 正常運作 Closed --> Open: 超過故障閾值 Open --> HalfOpen: 逾時已過 HalfOpen --> Closed: 成功 HalfOpen --> Open: 失敗 note right of Closed 請求通過 失敗時重試 end note note right of Open 請求立即失敗 不嘗試重試 end note note right of HalfOpen 允許有限請求 測試服務恢復 end note

這些模式一起提供全面的故障處理:

  1. 重試處理暫時性故障
  2. 斷路器防止壓垮失敗的服務
  3. 即使在長時間中斷期間,系統仍保持回應

相關模式

斷路器:防止應用程式重複嘗試執行可能失敗的操作,使其能夠繼續運作而無需等待故障修復。

節流:控制應用程式實例、服務或租戶的資源消耗。

速率限制:管理傳送到服務的請求速率,以避免壓垮它。

參考資料

分享到