iT邦幫忙

2025 iThome 鐵人賽

DAY 12
1
Software Development

Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程系列 第 12

Day12 - 錯誤難免,重試要有策略:Retry Policy 的設計思維

  • 分享至 

  • xImage
  •  

1. Activity Retry 的設計

1.1 Temporal 的 Retry Policy 的類型

類型 作用 典型場景 設定建議
maxAttempts 限制最大重試次數,控制成本與風險 短任務(API 呼叫)、高成本任務(付費/重 IO) 短任務 3–5、高成本 2–3、批次/低優先 5–7;與 initialInterval/backoff/maximumInterval 一起估算總重試時間,需 ≤ 單次/整體 Timeout
initialInterval 第一次重試等待時間 上游抖動、瞬時錯誤 以上游 P95 為基準:短連線/快查詢 100–300ms、人機互動 200–500ms、第三方較慢 1–2s;與 maxAttempts/backoff/maximumInterval 合併估算總重試時間 ≤ 單次 Timeout;越即時 initialInterval 越短、maxAttempts 越少
backoffCoefficient 每次重試逐步延長間隔 上游部分故障、做壓力緩釋 2.0(指數退避);過大易拖長總時間
maximumInterval 單次等待的上限 避免重試等待過長 建議 20–60s(短任務 5–15s、第三方較慢 20–60s);須 ≤ 單次 Timeout;與 backoffCoefficient 一起限制總重試時間,避免同時喚醒尖峰
nonRetryableErrorTypes 標記不可重試的錯誤型別 驗證失敗、資源不存在、權限問題、業務失敗 例如 InvalidInput/InvalidState/PermissionDenied/DuplicateRequest

1.2 Retry 的設計思維

  • 原則:

    • 優先在 Activity 層設定重試;避免在 Workflow 層包整體重試。
    • 明確標記 nonRetryable(永久性/業務失敗),縮短失敗回饋時間。
    • 退避要有上限(maximumInterval)。
    • 冪等性必備,避免重複執行副作用。
  • 快速檢查:

    • maximumInterval 是否 ≤ Start-to-Close?
    • 重試等待總和是否 ≤ 步驟總上限(例如 Schedule-to-Close)?
    • nonRetryable 型別是否完整(例如 InvalidInput/InvalidState 等)?
  • 常見錯誤:

    • 把整體重試放在 Workflow 層,造成重播與副作用風險。
    • 退避無上限或過長,導致等待時間不可控。
    • 忽略冪等鍵,重試造成重複扣款/重複寫入。

1.3 程式碼範例

ActivityOptions options = ActivityOptions.newBuilder()
    .setStartToCloseTimeout(Duration.ofSeconds(10))
    .setScheduleToStartTimeout(Duration.ofSeconds(5))
    .setRetryOptions(RetryOptions.newBuilder()
        .setMaximumAttempts(5)                   // 最大重試次數(含首次共最多 5 次)
        .setInitialInterval(Duration.ofSeconds(1)) // 第一次重試等待 1 秒
        .setBackoffCoefficient(2.0)              // 指數退避倍數(1s→2s→4s→…)
        .setMaximumInterval(Duration.ofSeconds(20)) // 單次等待上限,避免退避過長
        .setDoNotRetry("InvalidInput", "InvalidState") // 不可重試的永久性/業務錯誤型別
        .build())
    .build();
// 以上述 options 建立活動 stub,Workflow 內呼叫時自動套用
MyActivities acts = Workflow.newActivityStub(MyActivities.class, options);

上一篇談到 Timeout 的觀念,本篇則探討了 Retry 的設計思維,兩者相互搭配,才能兼顧流程的正確性與系統的穩定性。

2. Timeout 與 Retry 策略的演進(版本升級與動態調整)

  • 單次執行開始 → Timeout(或暫時性錯誤)→ 重試等待 → 下一次執行 → 成功或達到最大嘗試 → 失敗。
  • Timeout 與 Retry 不是一次設定就永遠固定,而是應該 隨著流量、失敗模式、下游 SLA 不斷調整。
  • Timeout 太短 → Retry 太頻繁 → 系統壓力放大。
  • Timeout 太長 → Worker 執行緒浪費 → 系統吞吐下降。
  • 關鍵:Timeout 與 Retry 必須一起調校。
  • 經驗法則:讓「單次 Timeout ≈ 外部作業 P99 ×2」,「最大退避間隔 ≤ 單次 Timeout」,避免同時大量超時與重試。

2.1 上線初期:保守策略

  • Timeout:設短一點,避免 Worker 執行緒被卡死。
  • Retry:次數設少一點,避免一開始就重壓下游。
  • 目標:收集真實運行數據,先確保系統不會自爆。

2.2 運行中:根據數據調整

  • 收集近 7–30 天的 P95/P99 latency。
  • 調整 Timeout:
    • Start-to-Close ≈ P99 × 2。
    • Heartbeat interval ≈ 一個穩定子批的處理時間,HeartbeatTimeout ≈ 2–3 倍。
  • 調整 Retry:
    • initialInterval ≈ P95(或其 0.5–1 倍)。
    • maximumInterval ≤ Start-to-Close。
    • maxAttempts 根據 SLA 微調,確保總重試時間 ≤ 單步 Timeout。

2.3 進階:動態策略

  • 使用動態設定或 feature flag,將 Timeout / RetryOptions 從程式碼抽離:
  • 例如在高峰期,臨時把 maxAttempts 從 5 改成 2,避免雪崩。
  • 或者將 maximumInterval 調大,讓下游有恢復空間。
  • 不需要重啟 Worker,就能即時生效。

2.4 策略演進原則

  • 一開始保守,避免過度重試。
  • 隨著數據累積,用 P95/P99 作為調整依據。
  • 持續觀測,必要時動態調整。
  • Timeout 與 Retry 必須聯動調校,不能單獨看。

2.5 常見反模式

  • 無限重試 → 下游雪崩。
  • 不分 transient / permanent error → 白費力氣。
  • Timeout 與 Retry 沒協調 → retry storm。
  • 心跳沒設 → 大任務重跑。

3. 補充:P95/P99

在這兩個篇幅 Timeout, Retry 都有提到 P95/P99,在此做一個補充。

– 定義:P95 表示 95% 的請求在此時間內完成(P99=99%)。用多數請求的真實 Latency 來定參數,×2 留安全緩衝。
– 取樣:近 7–30 天的實際 Latency,分環境/地區/端點各自量測。
– 視覺化:用直方圖觀察 Latency,比分別盯平均值更有意義。
– 常見誤解:P95/P99 是 Latency 分位數,不是成功率;P99 對樣本量敏感,需足夠時間窗(7–30 天)。

應用:

  1. 取數:取得目標端點的 P95、P99。
  2. 設單次 Timeout:短任務 Start-to-Close ≈ P99 × 2。
  3. 設 initialInterval:≈ P95(或其 0.5–1 倍)。
  4. 設 maximumInterval 並檢核:maximumInterval ≤ Start-to-Close;計算重試等待的幾何和(總重試時間)需 ≤ Start-to-Close。
  5. 長任務 Heartbeat:以「一次穩定子批的處理時間」為回報間隔;HeartbeatTimeout ≈ 間隔 × 2–3。
  6. 上線後回看:流量高峰或改版後重看 P95/P99,再微調 Timeout/Retry。

例子:

  • 步驟 1(量測):P95=420ms、P99=900ms。
  • 步驟 2(先設 Timeout/初始間隔):Start-to-Close≈1.8s(900ms×2)、initialInterval≈0.42s(≈P95)。
  • 步驟 3(估算重試等待;maxAttempts=5、backoff=2.0、maximumInterval=1.8s):
    0.42s → 0.84s → 1.68s → 1.8s(共 4 次),合計≈4.74s。
  • 檢核:重試等待總和(4.74s)是否 ≤ 步驟總上限(例如 Schedule-to-Close)?若超過,調整:減少 maxAttempts、縮短 maximumInterval,或拉長步驟總上限。

結語

Timeout 設得過長,Worker 會被任務佔住;設得過短,任務還沒完成就被中斷。
Retry 次數過多,下游會被重試壓垮;次數過少,暫時錯誤得不到恢復。
流程要可靠穩定,不只在開發時要精心設計,維運過程中也需要持續觀察與調整。

下一篇將談 併發與速率限制,從另一個角度來保護系統資源。


上一篇
Day11 - 不要讓流程無限等:Timeout Policy 的設計思維
下一篇
Day13 - 流量高峰不失控:併發與限速的設計指南
系列文
Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言