介紹
在軟體開發中,可靠的重試邏輯對於處理間歇性故障(例如網路問題或臨時中斷)至關重要。最近,我遇到了一個代碼庫,其中一位開發者使用了帶有固定時間間隔的for
循環來重試失敗的操作。雖然這種方法看起來很簡單,但它缺乏現實應用所需的彈性。這就是指數退避的用武之地——這是一種旨在使重試更智能、更高效的策略。
在本文中,我們將探討指數退避的工作原理、它相對於基本重試循環的優勢,以及如何實施它以增強系統的可靠性。我還將通過一個實用的示例,使用電子郵件發送模塊,向您展示如何使用指數退避來確保更有彈性的錯誤處理。
什麼是指數退避?
指數退避是一種重試策略,其中每次重試嘗試之間的等待時間在每次失敗後呈指數增長。與其在固定時間間隔內重試,每次後續嘗試的等待時間都比前一次更長——通常每次延遲翻倍。例如,如果初始延遲為1秒,則接下來的重試將在2秒、4秒、8秒等時進行。這種方法有助於減少系統負擔,並在需求高峰期間最小化對外部服務的壓力。
通過在重試之間留出更多的時間,指數退避給臨時問題提供了解決的機會,從而導致更高效的錯誤處理和改善的應用穩定性。
指數退避的優缺點
優點:
-
減少系統負載: 透過間隔重試,指數退避能夠減少伺服器過載的機會,特別適用於處理速率限制或瞬時故障。
-
有效的錯誤處理: 增加的延遲使瞬時問題有更多時間自然解決,提高成功重試的可能性。
-
穩定性提升: 對於高流量系統特別有效,能防止重試請求的洪水,使應用程式平穩運行而不會過度消耗資源。
缺點:
- 延遲增加: 每次重試所需的時間逐漸增加,指數退避可能導致延遲,特別是在成功之前需要多次重試的情況下。
指數退避的關鍵使用案例
指數退避在系統與外部服務交互或管理大量流量的場景中特別有用。以下是其他一些常見的使用案例:
-
限制速率的 API: 有些 API 有速率限制,限制在特定時間內的請求數量。指數退避有助於避免立即重試,這可能會超過限制,給予時間讓限制重置。
-
網路不穩定: 在暫時的網路故障或逾時的情況下,指數退避透過延長嘗試之間的等待時間來幫助,讓網路穩定下來。
-
資料庫連接: 當在高負載下連接資料庫時,指數退避可以透過延遲重試來防止進一步的過載,給予資料庫恢復的時間。
-
佇列系統: 在消息佇列系統中,如果消息因錯誤而失敗,使用指數退避進行重試可以防止快速重新處理,並允許時間解決暫時性問題。
建立一個基本的電子郵件發送服務,使用指數退避
為了展示指數退避,我們將建立一個基本的電子郵件發送器,如果發生錯誤,則重新嘗試發送電子郵件。這個例子顯示了指數退避如何改善重新嘗試過程,相較於簡單的for迴圈。
import nodemailer from "nodemailer";
import { config } from "../common/config";
import SMTPTransport from "nodemailer/lib/smtp-transport";
const emailSender = async (
subject: string,
recipient: string,
body: string
): Promise<boolean> => {
const transport = nodemailer.createTransport({
host: config.EMAIL_HOST,
port: config.EMAIL_PORT,
secure: true,
auth: { user: config.EMAIL_SENDER, pass: config.EMAIL_PASSWORD },
} as SMTPTransport.Options);
const mailOptions: any = {
from: config.EMAIL_SENDER,
to: recipient,
subject: subject,
};
const maxRetries = 5; // maximum number of retries before giving up
let retryCount = 0;
let delay = 1000; // initial delay of 1 second
while (retryCount < maxRetries) {
try {
// send email
await transport.sendMail(mailOptions);
return true;
} catch (error) {
// Exponential backoff strategy
retryCount++;
if (retryCount < maxRetries) {
const jitter = Math.random() * 1000; // random jitter(in seconds) to prevent thundering herd problem
const delayMultiplier = 2
const backOffDelay = delay * delayMultiplier ** retryCount + jitter;
await new Promise((resolve) => setTimeout(resolve, backOffDelay));
} else {
// Log error
console.log(error)
return false; // maximum number of retries reached
}
}
}
return false;
};
調整指數退避參數
實施指數退避涉及調整某些參數,以確保重試策略能適合您的應用需求。以下關鍵參數影響指數退避在重試機制中的行為和性能:
- 初始延遲
-
目的: 設定第一次重試之前的等待時間。應該足夠長以防止立即重試,但又要足夠短以避免明顯的延遲。
-
建議設定: 從 500 毫秒 到 1000 毫秒 的延遲開始。對於關鍵系統,使用較短的延遲,而不那麼緊急的操作可以有較長的延遲。
- 延遲乘數
-
用途: 控制每次重試後延遲增加的速度。乘數為 2 時,延遲加倍(例如,1秒、2秒、4秒)。
-
推薦設置: 通常,乘數在 1.5 和 2 之間可以平衡響應速度和穩定性。如果系統可以處理更長的重試延遲,則較高的乘數(例如,3)可能適合。
- 最大重試次數
-
用途: 限制重試次數,以防止過多的重試耗盡資源或增加系統負擔。
-
推薦設置: 通常,3 到 5 次重試 對於大多數應用程序來說已經足夠。超過此次數後,操作可能需要記錄為失敗,或以其他方式管理,例如通知用戶或觸發警報。
- 抖動(隨機化)
-
目的: 為每次延遲添加隨機性,以防止重試聚集造成雷鳴效應。
-
建議設定: 在每次重試間隔中添加 0 到 500 毫秒 的隨機延遲。這種抖動有助於在時間上更均勻地分配重試嘗試。
結論
透過使用指數退避,您可以為應用程式增加韌性,準備處理意外問題。這是一個小改變,但影響重大,特別是當您的應用程式增長時。
目前就這些,大家隨時可以留言,如果有任何問題請隨時詢問。祝願大家構建更可靠和更具韌性的應用程式。
快樂編程!👨💻❤️
Source:
https://timothy.hashnode.dev/implementing-exponential-backoff-for-reliable-systems