當你的應用程式與遠端服務通訊時——資料庫、API、訊息佇列——可能會出錯。網路中斷、伺服器忙碌或暫時性逾時都可能導致請求失敗。重試模式幫助你的應用程式優雅地處理這些暫時性故障,將潛在的失敗轉化為成功。
情境與問題
分散式系統經常面臨暫時性故障:
- 網路連線中斷:元件之間的短暫斷線
- 服務不可用:部署或重啟期間的暫時性服務中斷
- 逾時:服務在高負載下回應時間過長
- 節流:服務在過載時拒絕請求
這些故障通常會自我修正。暫時過載的資料庫可能現在拒絕你的連線,但在清除積壓工作後一秒鐘就會接受。如果沒有重試機制,你的應用程式會將這些暫時性問題視為永久性故障,不必要地降低使用者體驗。
解決方案
設計你的應用程式以預期暫時性故障並透明地處理它們。重試模式引入一種機制,自動重試失敗的操作,將對業務功能的影響降到最低。
💡 內建重試機制
許多現代客戶端函式庫和框架都包含可設定的重試邏輯。在實作自訂重試程式碼之前,請先查看你的函式庫文件。
!!!
重試策略
根據故障類型和應用程式需求選擇重試策略:
1. 取消
何時使用:故障表示永久性問題或即使重試也不會成功的操作。
範例:
- 驗證失敗
- 無效的請求參數
- 找不到資源錯誤
動作:立即取消操作並回報例外。
2. 立即重試
何時使用:故障不尋常或罕見,例如網路封包損毀。
範例:
- 隨機網路傳輸錯誤
- 暫時性連線重設
動作:立即重試請求,不延遲。
3. 延遲後重試
何時使用:故障常見且與連線或服務負載相關。
範例:
- 連線逾時
- 服務忙碌回應
- 節流錯誤
動作:等待後再重試,使用以下延遲策略之一:
固定延遲:每次重試之間等待相同的時間。
嘗試 1 → 等待 2 秒 → 嘗試 2 → 等待 2 秒 → 嘗試 3
遞增延遲:線性增加等待時間。
嘗試 1 → 等待 2 秒 → 嘗試 2 → 等待 4 秒 → 嘗試 3 → 等待 6 秒 → 嘗試 4
指數退避:每次失敗後將等待時間加倍。
嘗試 1 → 等待 1 秒 → 嘗試 2 → 等待 2 秒 → 嘗試 3 → 等待 4 秒 → 嘗試 4 → 等待 8 秒 → 嘗試 5
帶抖動的指數退避:在指數延遲中加入隨機性,以防止多個客戶端同時重試(「驚群」問題)。
實作考量
記錄策略
適當記錄故障以避免警報疲勞:
- 早期故障:記錄為資訊項目
- 成功重試:記錄在除錯層級
- 最終故障:僅在所有重試都用盡後記錄為錯誤
這種方法為操作人員提供可見性,而不會用自我修正問題的警報淹沒他們。
效能影響
調整重試策略以符合業務需求:
互動式應用程式(網頁應用程式、行動應用程式):
- 快速失敗,較少重試次數
- 嘗試之間使用短延遲
- 顯示使用者友善訊息(「請稍後再試」)
批次應用程式(資料處理、ETL 作業):
- 使用更多重試嘗試
- 採用指數退避與較長延遲
- 優先考慮完成而非速度
⚠️ 避免激進重試
激進的重試策略(許多重試且延遲最小)可能會使情況惡化:
- 進一步降低已經過載的服務
- 降低應用程式的回應性
- 在系統中造成級聯故障
考慮在重試之外實作斷路器模式,以防止壓垮失敗的服務。
!!!
冪等性
在應用重試之前,確保操作是冪等的(多次執行是安全的)。非冪等操作可能導致意外的副作用:
問題情境:
- 服務接收請求並成功處理
- 服務因網路問題無法傳送回應
- 客戶端重試,導致重複處理
解決方案:
- 設計操作為自然冪等
- 使用唯一請求識別碼來偵測重複
- 實作伺服器端去重邏輯
例外類型
不同的例外需要不同的重試策略:
例外類型 | 重試策略 | 範例 |
---|---|---|
暫時性網路錯誤 | 延遲後重試 | 連線逾時、DNS 解析失敗 |
服務忙碌/節流 | 指數退避重試 | HTTP 429、HTTP 503 |
驗證失敗 | 立即取消 | 無效憑證、過期權杖 |
無效請求 | 立即取消 | HTTP 400、格式錯誤的資料 |
找不到資源 | 立即取消 | HTTP 404 |
交易一致性
在交易中重試操作時:
- 微調重試策略以最大化成功機率
- 最小化回滾交易步驟的需求
- 考慮分散式情境的補償交易
- 確保重試邏輯不違反交易隔離層級
測試與驗證
🧪 測試檢查清單
- 針對各種故障條件進行測試(逾時、連線錯誤、服務不可用)
- 驗證正常和故障情境下的效能影響
- 確認下游服務沒有過度負載
- 檢查並行重試的競爭條件
- 驗證不同故障階段的記錄輸出
- 測試交易回滾情境
!!!
巢狀重試策略
避免分層多個重試策略:
問題:任務 A(有重試策略)呼叫任務 B(也有重試策略)。這會造成指數級的重試嘗試和不可預測的延遲。
解決方案:設定低層級任務快速失敗並回報故障。讓高層級任務根據自己的策略處理重試。
(重試策略)"] --> B["任務 B
(無重試)"] B -->|"快速失敗"| A A -->|"根據策略重試"| B style A fill:#e1f5ff style B fill:#fff4e1
何時使用此模式
使用重試模式當:
- 你的應用程式與遠端服務或資源互動
- 故障預期是暫時性且短暫的
- 重複失敗的請求有很大機會成功
- 操作是冪等的或可以變成冪等的
不要使用重試模式當:
- 故障可能是長期的(改用斷路器)
- 處理非暫時性故障(業務邏輯錯誤、驗證失敗)
- 解決可擴展性問題(改為擴展服務)
- 操作有重大副作用且不是冪等的
與斷路器結合
重試和斷路器模式相輔相成:
- 重試:透過再次嘗試操作來處理暫時性故障
- 斷路器:當已知服務停機時防止重試
這些模式一起提供全面的故障處理:
- 重試處理暫時性故障
- 斷路器防止壓垮失敗的服務
- 即使在長時間中斷期間,系統仍保持回應
相關模式
斷路器:防止應用程式重複嘗試執行可能失敗的操作,使其能夠繼續運作而無需等待故障修復。
節流:控制應用程式實例、服務或租戶的資源消耗。
速率限制:管理傳送到服務的請求速率,以避免壓垮它。