重試模式

Azure

藉由以透明方式重試失敗的作業,讓應用程式在嘗試連線到服務或網路資源時處理暫時性失敗。 這可以改善應用程式的穩定性。

內容和問題

應用程式若要與在雲端中執行的元素通訊,必須能感應在此環境中發生的暫時性錯誤。 錯誤包括暫時遺失對元件和服務的網路連線、暫時無法使用服務,或服務忙碌時發生的逾時。

這些錯誤通常是自我更正,而且如果觸發錯誤的動作在適當延遲之後重複,可能會成功。 例如,處理大量並行要求的資料庫服務可以實 作節流策略 ,以暫時拒絕任何進一步的要求,直到工作負載放寬為止。 嘗試存取資料庫的應用程式可能無法連線,但如果應用程式在延遲之後再次嘗試,可能會成功。

解決方案

在雲端中,暫時性錯誤並不常見,而且應用程式應該設計成以優雅且透明的方式處理它們。 這可將錯誤對應用程式執行之商務工作的影響降到最低。

如果應用程式嘗試將要求傳送至遠端服務時偵測到失敗,則可以使用下列策略來處理失敗:

  • 取消。 如果錯誤指出失敗不是暫時性的,或者如果重複,則應用程式應該取消作業並回報例外狀況。 例如,提供無效認證所造成的驗證失敗不太可能成功,無論嘗試多少次。

  • 重試。 如果報告的特定錯誤不尋常或罕見,可能是因為網路封包在傳輸時損毀等異常情況所造成。 在此情況下,應用程式可能會立即重試失敗要求,因為不太可能重複相同的失敗,而且要求可能會成功。

  • 延遲後重試。 如果錯誤是由其中一個較常見的連線或忙碌失敗所造成,當聯機問題修正或清除工作積壓時,網路或服務可能需要一小段時間。 應用程式應該在重試要求之前等候適當的時間。

針對較常見的暫時性失敗,應選擇重試之間的期間,盡可能平均地分散來自應用程式的多個實例的要求。 這樣可減少忙碌服務繼續超載的機會。 如果應用程式的許多實例持續讓服務承受重試要求,則服務需要較長的時間才能復原。

如果要求仍然失敗,應用程式可以等候並嘗試另一次。 如有必要,此程式可以重複,並增加重試嘗試之間的延遲,直到嘗試了一些要求數目上限為止。 延遲可以累加或指數方式增加,視失敗類型以及在此期間更正的機率而定。

下圖說明使用此模式叫用託管服務中的作業。 如果要求在預先定義的嘗試次數之後失敗,應用程式應該將錯誤視為例外狀況並據以處理。

圖 1 - 使用重試模式叫用託管服務中的作業

應用程式應該包裝所有嘗試,以程式代碼存取遠端服務,以實作符合上述其中一個策略的重試原則。 傳送至不同服務的要求可能會受限於不同的原則。 有些廠商提供實作重試原則的連結庫,其中應用程式可以指定重試次數上限、重試嘗試之間的時間,以及其他參數。

應用程式應該記錄錯誤和失敗作業的詳細數據。 這項資訊對運算子很有用。 也就是說,為了避免洪水操作員收到後續重試嘗試成功的作業警示,最好將早期失敗記錄為 參考專案 ,並只將最後一次重試嘗試失敗的失敗記錄為實際錯誤。 以下是此記錄模型的外觀範例。

如果服務經常無法使用或忙碌,通常是因為服務已耗盡其資源。 您可以藉由相應放大服務來減少這些錯誤的頻率。 例如,如果資料庫服務持續多載,則分割資料庫並將負載分散到多部伺服器可能很有説明。

Microsoft Entity Framework 提供重試資料庫作業的功能。 此外,大部分的 Azure 服務和用戶端 SDK 都包含重試機制。 如需詳細資訊,請參閱 特定服務的重試指引。

問題和考慮

決定如何實作此模式時,您應該考慮下列幾點。

應調整重試原則,以符合應用程式的商務需求和失敗的性質。 對於某些非關鍵作業,最好快速失敗,而不是重試數次,並影響應用程式的輸送量。 例如,在存取遠端服務的互動式 Web 應用程式中,最好是在重試嘗試之間只有短暫延遲的少量重試之後失敗,並向使用者顯示適當的訊息(例如,「請稍後再試一次」)。 針對批次應用程式,可能更適合增加嘗試次數,並增加嘗試之間的延遲指數。

嘗試與大量重試之間延遲最少的主動式重試原則可能會進一步降低接近或容量的忙碌服務。 如果應用程式持續嘗試執行失敗的作業,此重試原則也會影響應用程式的回應性。

如果要求在大量重試之後仍然失敗,應用程式最好防止進一步的要求移至相同的資源,並直接立即回報失敗。 當期間到期時,應用程式可以暫時允許一或多個要求,以查看其是否成功。 如需此策略的詳細資訊,請參閱 斷路器模式

請考慮作業是否為等冪。 如果是,則重試原本就很安全。 否則,重試可能會導致作業多次執行,併產生非預期的副作用。 例如,服務可能會接收要求、成功處理要求,但無法傳送回應。 此時,重試邏輯可能會重新傳送要求,前提是未收到第一個要求。

針對服務的要求可能會因為各種原因而失敗,而引發不同的例外狀況,視失敗的性質而定。 某些例外狀況表示可快速解決的失敗,而其他例外狀況則表示失敗持續時間較長。 重試原則適合根據例外狀況類型調整重試嘗試之間的時間。

請考慮重試屬於交易一部分的作業將如何影響整體交易一致性。 微調交易作業的重試原則,以最大化成功的機會,並減少復原所有交易步驟的需求。

請確定所有重試程式代碼都已針對各種失敗狀況進行完整測試。 檢查它不會影響應用程式的效能或可靠性、對服務和資源造成過多負載,或產生競爭狀況或瓶頸。

只有在了解失敗作業的完整內容時,才實作重試邏輯。 例如,如果包含重試原則的工作叫用另一個同時包含重試原則的工作,這個額外的重試層可能會為處理增加長時間的延遲。 最好將較低層級的工作設定為快速失敗,並將失敗的原因回報給叫用它的工作。 此較高層級的工作接著可以根據自己的原則來處理失敗。

請務必記錄導致重試的所有連線失敗,以便識別應用程式、服務或資源的根本問題。

調查服務或資源最有可能發生的錯誤,以探索它們是否可能是長期或終端機。 如果是,最好以例外狀況處理錯誤。 應用程式可以報告或記錄例外狀況,然後嘗試叫用替代服務(如果有的話),或提供降級的功能來繼續。 如需如何偵測及處理長期錯誤的詳細資訊,請參閱 斷路器模式

使用此模式的時機

當應用程式與遠端服務互動或存取遠端資源時,可能會發生暫時性錯誤時,請使用此模式。 這些錯誤預期會短暫存在,而且重複先前失敗的要求可能會在後續嘗試時成功。

此模式可能沒有用處:

  • 當錯誤可能持續很長時間時,因為這可能會影響應用程式的回應性。 應用程式可能會浪費時間和資源,而嘗試重複可能會失敗的要求。
  • 針對處理因暫時性錯誤而造成的失敗,例如應用程式商業規則錯誤所造成的內部例外狀況。
  • 作為解決系統中延展性問題的替代方案。 如果應用程式遇到經常忙碌的錯誤,通常表示應該相應增加要存取的服務或資源。

工作負載設計

架構設計人員應該評估如何在工作負載的設計中使用重試模式,以解決 Azure 架構架構要素涵蓋的目標和原則。 例如:

要素 此模式如何支援支柱目標
可靠性設計決策可協助工作負載復原到故障,並確保它會在發生失敗后復原到完全正常運作的狀態。 減輕分散式系統中的暫時性錯誤是改善工作負載復原能力的核心技術。

- RE:07 自我保護
- RE:07 暫時性錯誤

如同任何設計決策,請考慮對其他可能以此模式導入之目標的任何取捨。

範例

C# 中的這個範例說明重試模式的實作。 如下所示 OperationWithBasicRetryAsync 的方法會透過 TransientOperationAsync 方法以異步方式叫用外部服務。 方法的詳細數據 TransientOperationAsync 將專屬於服務,而且會從範例程式代碼中省略。

private int retryCount = 3;
private readonly TimeSpan delay = TimeSpan.FromSeconds(5);

public async Task OperationWithBasicRetryAsync()
{
  int currentRetry = 0;

  for (;;)
  {
    try
    {
      // Call external service.
      await TransientOperationAsync();

      // Return or break.
      break;
    }
    catch (Exception ex)
    {
      Trace.TraceError("Operation Exception");

      currentRetry++;

      // Check if the exception thrown was a transient exception
      // based on the logic in the error detection strategy.
      // Determine whether to retry the operation, as well as how
      // long to wait, based on the retry strategy.
      if (currentRetry > this.retryCount || !IsTransient(ex))
      {
        // If this isn't a transient error or we shouldn't retry,
        // rethrow the exception.
        throw;
      }
    }

    // Wait to retry the operation.
    // Consider calculating an exponential delay here and
    // using a strategy best suited for the operation and fault.
    await Task.Delay(delay);
  }
}

// Async method that wraps a call to a remote service (details not shown).
private async Task TransientOperationAsync()
{
  ...
}

叫用這個方法的語句包含在包裝在 for 迴圈中的 try/catch 區塊中。 如果方法的呼叫成功而沒有擲回例外狀況, TransientOperationAsync for 迴圈就會結束。 TransientOperationAsync如果方法失敗,catch 區塊會檢查失敗的原因。 如果認為這是暫時性錯誤,程式代碼會在重試作業之前等候短暫的延遲。

for 迴圈也會追蹤已嘗試作業的次數,如果程式代碼失敗三次,則會假設例外狀況更持久。 如果例外狀況不是暫時性的,或是長時間發生,catch 處理程式會擲回例外狀況。 這個例外狀況會結束 for 迴圈,而且應該由叫用 方法的程式代碼 OperationWithBasicRetryAsync 攔截。

如下所示 IsTransient 的方法會檢查與程式代碼執行環境相關的特定例外狀況集。 暫時性例外狀況的定義會根據所存取的資源和正在執行作業的環境而有所不同。

private bool IsTransient(Exception ex)
{
  // Determine if the exception is transient.
  // In some cases this is as simple as checking the exception type, in other
  // cases it might be necessary to inspect other properties of the exception.
  if (ex is OperationTransientException)
    return true;

  var webException = ex as WebException;
  if (webException != null)
  {
    // If the web exception contains one of the following status values
    // it might be transient.
    return new[] {WebExceptionStatus.ConnectionClosed,
                  WebExceptionStatus.Timeout,
                  WebExceptionStatus.RequestCanceled }.
            Contains(webException.Status);
  }

  // Additional exception checking logic goes here.
  return false;
}

下一步

  • 在撰寫自定義重試邏輯之前,請考慮使用一般架構,例如 適用於 .NET 的 Polly適用於 Java 的 Resilience4j

  • 處理變更商務數據的命令時,請注意重試可能會導致執行兩次動作,如果該動作像是向客戶的信用卡收費,可能會造成問題。 使用此部落格文章中所述的等冪模式有助於處理這些情況。

  • 可靠的 Web 應用程式模式 示範如何將重試模式套用至聚合在雲端上的 Web 應用程式。

  • 針對大部分的 Azure 服務,用戶端 SDK 包含內建的重試邏輯。 如需詳細資訊,請參閱 Azure 服務的重試指引。

  • 斷路器模式。 如果失敗預期更持久,可能更適合實作斷路器模式。 結合重試和斷路器模式提供處理錯誤的完整方法。