Java Retry 重試機制 - 指數退避模版
遠端呼叫爆了,重試一下通常就活了。但如果一直狂轟,server 只會更爆。所以咱們要聰明一點,用指數退避(exponential backoff)的方式慢慢來。
為啥要指數退避
直接 retry 沒有 delay 的問題:
- Server 還沒回過來,馬上再打一次,等於雪上加霜
- 頻繁重試反而加重服務負擔
- 看起來像 DDoS 攻擊 XD
指數退避的邏輯:
- 第 1 次失敗,等 2 秒再試
- 第 2 次失敗,等 4 秒再試
- 第 3 次失敗,等 8 秒再試
- 以此類推…
給 server 喘息的時間,機率就高很多。
通用模版
1 | public boolean retry(int maxAttempts, int initialDelaySeconds, Runnable action) { |
怎麼用
最常見的場景就是打 HTTP 請求:
1 | retry(3, 2, () -> { |
或者是資料庫操作:
1 | retry(5, 1, () -> { |
要注意的坑
1. Delay 上限要加
指數增長如果不加上限,到第 10 次重試可能要等 1024 秒(快 17 分鐘了)。加個 cap:
1 | delay = Math.min(delay * 2, 60); // 最多等 60 秒 |
2. InterruptedException 要處理
當 Thread 被 interrupt 時,sleep() 會拋出 InterruptedException,要記得呼叫 Thread.currentThread().interrupt() 恢復狀態,不然其他地方檢查不到 interrupt flag。
3. 並不是所有 Exception 都該重試
網路超時、連線拒絕可以重試,但如果是參數錯誤、權限不足,重試再多次也沒用。可以加個 filter:
1 | public boolean retry(int maxAttempts, int initialDelaySeconds, |
4. Jitter 增加隨機性
在真實環境中,如果有多個客戶端同時 retry,都在相同的時間點同時重試,又會造成新的尖峰。可以加點隨機延遲(jitter):
1 | delay = (int)(Math.random() * initialDelaySeconds); // 亂數退避 |
實務建議
- 基本的初始延遲設 2 秒,最多重試 3 次
- API 呼叫用 10 秒、重試 5 次(因為網路不穩)
- 資料庫操作用 1 秒、重試 3 次
- 加日誌,這樣遇到問題才看得出來是 retry 成功還是全部爆了
這個模版夠通用,大部分場景 copy-paste 就能用。踩過的坑就寫在上面了,應該不會再踩一次。
本部落格所有文章除特別聲明外,均採用CC BY-NC-SA 4.0 授權協議。轉載請註明來源 Cheng's Tech & Life!
評論










