重試邏輯的正確實作:為可靠系統實現指數備援

在軟體開發中,可靠的重試邏輯對於處理間歇性故障(例如網路問題或臨時中斷)至關重要。最近,我遇到了一個代碼庫,其中一位開發者使用了帶有固定時間間隔的for循環來重試失敗的操作。雖然這種方法看起來很簡單,但它缺乏現實應用所需的彈性。這就是指數退避的用武之地——這是一種旨在使重試更智能、更高效的策略。

在本文中,我們將探討指數退避的工作原理、它相對於基本重試循環的優勢,以及如何實施它以增強系統的可靠性。我還將通過一個實用的示例,使用電子郵件發送模塊,向您展示如何使用指數退避來確保更有彈性的錯誤處理。

指數退避是一種重試策略,其中每次重試嘗試之間的等待時間在每次失敗後呈指數增長。與其在固定時間間隔內重試,每次後續嘗試的等待時間都比前一次更長——通常每次延遲翻倍。例如,如果初始延遲為1秒,則接下來的重試將在2秒、4秒、8秒等時進行。這種方法有助於減少系統負擔,並在需求高峰期間最小化對外部服務的壓力。

通過在重試之間留出更多的時間,指數退避給臨時問題提供了解決的機會,從而導致更高效的錯誤處理和改善的應用穩定性。

  • 減少系統負載: 透過間隔重試,指數退避能夠減少伺服器過載的機會,特別適用於處理速率限制或瞬時故障。

  • 有效的錯誤處理: 增加的延遲使瞬時問題有更多時間自然解決,提高成功重試的可能性。

  • 穩定性提升: 對於高流量系統特別有效,能防止重試請求的洪水,使應用程式平穩運行而不會過度消耗資源。

  • 延遲增加: 每次重試所需的時間逐漸增加,指數退避可能導致延遲,特別是在成功之前需要多次重試的情況下。

指數退避在系統與外部服務交互或管理大量流量的場景中特別有用。以下是其他一些常見的使用案例:

  1. 限制速率的 API: 有些 API 有速率限制,限制在特定時間內的請求數量。指數退避有助於避免立即重試,這可能會超過限制,給予時間讓限制重置。

  2. 網路不穩定: 在暫時的網路故障或逾時的情況下,指數退避透過延長嘗試之間的等待時間來幫助,讓網路穩定下來。

  3. 資料庫連接: 當在高負載下連接資料庫時,指數退避可以透過延遲重試來防止進一步的過載,給予資料庫恢復的時間。

  4. 佇列系統: 在消息佇列系統中,如果消息因錯誤而失敗,使用指數退避進行重試可以防止快速重新處理,並允許時間解決暫時性問題。

為了展示指數退避,我們將建立一個基本的電子郵件發送器,如果發生錯誤,則重新嘗試發送電子郵件。這個例子顯示了指數退避如何改善重新嘗試過程,相較於簡單的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;
};

實施指數退避涉及調整某些參數,以確保重試策略能適合您的應用需求。以下關鍵參數影響指數退避在重試機制中的行為和性能:

  1. 初始延遲
  • 目的: 設定第一次重試之前的等待時間。應該足夠長以防止立即重試,但又要足夠短以避免明顯的延遲。

  • 建議設定:500 毫秒1000 毫秒 的延遲開始。對於關鍵系統,使用較短的延遲,而不那麼緊急的操作可以有較長的延遲。

  1. 延遲乘數
  • 用途: 控制每次重試後延遲增加的速度。乘數為 2 時,延遲加倍(例如,1秒、2秒、4秒)。

  • 推薦設置: 通常,乘數在 1.52 之間可以平衡響應速度和穩定性。如果系統可以處理更長的重試延遲,則較高的乘數(例如,3)可能適合。

  1. 最大重試次數
  • 用途: 限制重試次數,以防止過多的重試耗盡資源或增加系統負擔。

  • 推薦設置: 通常,3 到 5 次重試 對於大多數應用程序來說已經足夠。超過此次數後,操作可能需要記錄為失敗,或以其他方式管理,例如通知用戶或觸發警報。

  1. 抖動(隨機化)
  • 目的: 為每次延遲添加隨機性,以防止重試聚集造成雷鳴效應。

  • 建議設定: 在每次重試間隔中添加 0 到 500 毫秒 的隨機延遲。這種抖動有助於在時間上更均勻地分配重試嘗試。

透過使用指數退避,您可以為應用程式增加韌性,準備處理意外問題。這是一個小改變,但影響重大,特別是當您的應用程式增長時。

目前就這些,大家隨時可以留言,如果有任何問題請隨時詢問。祝願大家構建更可靠和更具韌性的應用程式。

快樂編程!👨‍💻❤️

Source:
https://timothy.hashnode.dev/implementing-exponential-backoff-for-reliable-systems