Java中使用指數退避和抖動實現重試

banq發表於2024-10-15

問題

  • 您正在設計一個需要與外部 API 通訊的服務,該服務偶爾會因暫時的網路問題而失敗。請描述您將如何實施重試機制來處理這些故障。
  • 接下來,解釋一下何時使用斷路器而不是重試機制,並討論同時實現兩者的場景。

使用指數退避和抖動進行重試

  • 當與外部 API 通訊以處理瞬時網路問題時,我們應該實現重試機制來自動重試失敗的請求。
  • 重試機制會嘗試重新傳送請求有限次,然後放棄。為了實現這一點,我們將使用指數退避和抖動來確定重試之間的等待時間。此策略會隨著每次重試而呈指數增加退避時間,而抖動(隨機延遲)有助於分散重試請求,從而降低同時重試導致外部服務不堪重負的風險。

public class RetryWithExponentialBackoff {
    private static final int MAX_ATTEMPTS = 5;
    private static final long INITIAL_BACKOFF_MILLIS = 1000; <font>// 1 second<i>
    private static final long MAX_BACKOFF_MILLIS = 10000;
// 10 seconds<i>
    private static final Random RANDOM = new Random();

    public static void main(String[] args) {
        try {
            retryTask();
            System.out.println(
"Task completed successfully.");
        } catch (Exception e) {
            System.err.println(
"Task failed after retries: " + e.getMessage());
        }
    }

    private static void retryTask() throws Exception {
        int attempts = 0;
        while(attempts < MAX_ATTEMPTS){
            try {
                performTask();
                return;
            } catch (Exception e){
                attempts++;
                if(attempts >= MAX_ATTEMPTS){
                    throw new Exception(
"Max retry reached.", e);
                }

                long backOffTime = calculateBackOffWithJitter(attempts);
                System.err.printf(
"Attempt %d failed. Retrying in %d ms...%n", attempts, backOffTime);
                try {
                    Thread.sleep(backOffTime);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(
"Thread was interrupted during retry delay", ie);
                }
            }
        }
    }

   
//計算指數後退並增加抖動<i>
    private static long calculateBackOffWithJitter(int attempts) {
        double exponentialBackOff = Math.min(INITIAL_BACKOFF_MILLIS * Math.pow(2, attempts - 1), MAX_BACKOFF_MILLIS);
        return (long) (exponentialBackOff * RANDOM.nextDouble());
    }

   
// 隨機故障的任務,描述瞬時故障<i>
    private static void performTask(){
        if(Math.random() > 0.7){
            System.out.println(
"Task succeeded.");
        }else{
            throw new RuntimeException(
"Task failed.");
        }
    }
}

常量:

  • MAX_ATTEMPTS:重試的最大次數(在本例中為 5 次)。
  • INITIAL_BACKOFF_MILLIS:初始退避時間,以毫秒(1 秒)為單位。
  • MAX_BACKOFF_MILLIS:最大退避時間,以毫秒為單位(10 秒)。
  • RANDOM:Random用於產生抖動的物件。

retryTask方法:
  • 嘗試執行該performTask()方法,直至達到最大重試次數(MAX_ATTEMPTS)。
  • 如果任務失敗,重試機制將等待使用抖動指數退避演算法計算的退避時間。
  • 每次重試後退避時間都會增加,並且會新增抖動以防止同時重試導致伺服器不堪重負。

performTask方法:
  • 此方法模擬了可能隨機失敗的不可靠任務。
  • 如果任務成功(Math.random() > 0.7),則成功返回;否則,丟擲異常。

calculateBackoffWithJitter方法:
  • 指數退避計算:等待時間使用 計算INITIAL_BACKOFF_MILLIS * 2^(attempt-1)。退避上限為 ,MAX_BACKOFF_MILLIS以防止等待時間過長。
  • 抖動新增:退避時間隨後乘以 0 到 1 之間的隨機因子 ( RANDOM.nextDouble())。此隨機延遲會分散重試請求,並降低同時重試導致外部服務不堪重負的風險。

何時使用斷路器
  • 當我們通訊的服務持續出現故障時,我們應該使用斷路器。在這種情況下,重試請求只會增加不必要的負載並延遲恢復。 
  • 斷路器的工作原理是在預定義的連續故障次數後斷開連線。然後等待指定的時間,然後允許有限數量的請求來檢查外部服務是否已恢復。

具有重試機制的斷路器
  • 使用斷路器和重試機制是提高與外部服務互動的應用程式的彈性的常見模式。當服務持續失敗時,斷路器會阻止發出請求,而重試機制會使用再次嘗試請求的策略來處理瞬時故障。
  • 當與外部 API 通訊時,我們應該首先嚐試使用重試機制發出請求。如果發生故障,則延遲重試(使用指數退避和抖動等策略)以克服瞬態問題。
  • 現在,我們可以將整個重試邏輯包裝在斷路器中。斷路器會監控重試過程中的故障,如果超過指定的故障閾值,它會開啟並在一段時間內阻止後續請求。
  • 一旦斷路器開啟,它會阻止在給定時間內嘗試重試,從而避免使外部服務不堪重負。

相關文章