# 云計算設計模式(十八)——重試模式
啟用應用程序來處理預期的,暫時的失敗時,它會嘗試連接到由透明的重試操作了以前失敗的期望,失敗的原因是瞬時的服務或網絡資源。這種模式可以提高應用程序的穩定性。
### 背景和問題
該通信的應用程序與在云中運行的元素必須是可能發生在這樣的環境中的瞬時故障敏感。這些故障包括網絡連接的過程中出現時,一個服務是忙碌的瞬時損失的組件和服務中,服務的臨時不可用,或超時。
這些故障一般是自校正的,如果經過一個合適的延遲被重復觸發一個故障的動作很可能是成功的。例如,數據庫服務,它正在處理大量并發請求可以實現節流策略,暫時拒絕,直到它的工作量有所緩和任何進一步的請求。試圖訪問該數據庫的應用程序可能無法連接,但如果它經過一個合適的延遲再次嘗試它可能會成功。
### 解決方案
在云中,瞬時故障的情況并不少見和應用應該被設計為優雅和透明地處理它們,減少的影響,這種故障可能對應用程序正在執行業務任務。
如果一個應用程序檢測到故障時,它試圖將請求發送到遠程服務,它可以通過使用以下策略處理失敗:
- 如果故障指示故障不是瞬時的或不太成功,如果重復(例如,所造成的無效提供憑據的認證失敗是不可能成功的,無論是多少次未遂),應用程序應中止操作和報告一個合適的異常。
- 如果報道的具體故障是不尋常的或罕見的,這可能是由于反常的情況,如網絡數據包成為損壞,同時它被發送。在這種情況下,應用程序可以再次立即重試失敗的請求,因為相同的故障是不可能被重復和請求將可能是成功的。
- 如果故障是由一種更加普遍的連接,或“忙”的失敗,網絡或服務可能需要在短期內同時連接問題糾正或工作的積壓被清除。應用程序應該等待請求重試前一個合適的時間。
對于比較常見的短暫故障,重試期間,應選擇以傳播從應用程序中盡可能均勻的多個實例的請求。這可以減少繁忙的業務持續過載的可能性。如果一個應用程序的多個實例不斷轟擊與重試請求的服務,則可能需要該服務更長的時間來恢復。
如果請求仍然失敗,應用程序可以等待進一步的時期,再次嘗試。如果需要的話,這個過程可以重復而增加重試的延遲,直到請求的某一最大數目已經嘗試都失敗了。延遲時間可以逐步增加,或可使用的定時策略,如指數回退,取決于故障的性質和可能性,這將在這段時間內被校正。
圖1示出了這種模式。如果嘗試后的預定數量的請求不成功,應用程序應將故障為異常,并相應地處理它。

圖1 - 使用重試模式中調用托管服務的操作
應用程序應該換所有試圖訪問遠程服務,實現重試政策配套上面列出的策略之一的代碼。發送到不同的服務請求會受到不同的政策,有的供應商提供封裝這種方法庫。這些庫通常執行的政策是參數化的,而應用程序開發人員可以指定,如重試次數和重試之間的時間項的值。
在檢測故障和重試失敗的操作都應該記錄這些故障的詳細信息的應用程序的代碼。這個信息可能是有用的運算符。如果一個服務被頻繁報道為不可用或忙,往往是因為該服務已耗盡其資源。則可以減少與這些故障發生時通過換算出該服務的頻率。例如,如果數據庫服務正在不斷超載,它可能是有利的分區數據庫和負載分散到多個服務器。
> 注意: 微軟 Azure 提供了重試模式的廣泛支持。該模式與實踐瞬態故障處理塊允許應用程序通過一系列的重試策略來處理許多 Azure 服務瞬態故障。微軟實體框架版本6提供了用于重新嘗試數據庫操作。此外,許多在 Azure Service Bus 和 Azure 存儲的 API 透明地執行重試邏輯。
### 問題和注意事項
在決定如何實現這個模式時,您應考慮以下幾點:
- 重試政策應進行調整,以滿足應用和故障性質的業務需求。它可能是更好一些非關鍵操作失敗快而不是重試幾次,并影響應用程序的吞吐量。例如,在試圖訪問遠程服務的交互式Web應用程序,這可能是更好的重試之后用重試之間只有一個短的延遲的數量較少失敗,并顯示一個適當的消息給用戶(例如,“請稍后“),再次嘗試阻止應用程序變得反應遲鈍。對于批處理應用程序,它可以是更合適的,以增加重試嘗試的次數與嘗試之間的指數增加的延遲。
- 與嘗試之間最小的延遲和大量的重試的高攻擊重試的政策,可能會進一步降低正在接近運行或容量的占用。此重試策略也可能會影響應用程序的響應,如果它被不斷地在嘗試執行失敗的操作,而不是做有用功。
- 如果后一個顯著次數的重試請求仍然失敗,則可能是更好的應用程序,以防止進一步的請求將要在相同的資源為一個周期,并簡單地立即報告故障。當期限屆滿后,該應用程序可以暫時允許通過一個或多個請求,看看他們是否成功。對于這一策略的詳細信息,請參閱斷路器格局。
- 在由它實現了一個重試策略可能需要為冪等的應用程序調用的服務的操作。例如,發送到服務的請求可以被接收和處理成功,但是,由于瞬時故障,它可能無法發送響應,指示該處理已完成。然后在應用程序的重試邏輯可能試圖重復上沒有接收到所述第一請求的假定該請求。
- 一個請求到服務失敗可能由于各種原因而提出不同的異常,根據故障的性質。一些例外可指示故障,可以非常迅速地得到解決,而另一些可能表明該故障持續時間更長。可能是有益的重試策略,調整基于所述異常的類型的重試嘗試之間的時間。
- 考慮如何重試的操作是事務的一部分,會影響整體交易的一致性。這可能是有用的微調對于事務性操作的重試政策,最大限度地取得成功的機會,并減少需要撤消所有交易步驟。
- 確保所有重試代碼是完全針對各種故障條件下進行測試。檢查它不會嚴重影響應用程序的性能或可靠性,導致在服務和資源的過度負荷,或產生競態條件或瓶頸。
- 實現只在一個失敗的操作的全方面了解重試邏輯。例如,如果包含的重試策略任務調用另一個任務還包含一個重試策略,這個額外的重試的層可加長的延遲的處理。它可能是更好的配置的低級任務失敗快速并報告失敗返回調用它的任務的原因。然后這個更高級別的任務可以決定如何處理是根據它自己的策略失效。
- 記錄所有的連接故障,提示了重試,使潛在的問題與該應用程序,服務或資源可以被識別是很重要的。
- 研究是最有可能發生于一個服務或資源發現,如果它們有可能是持久或終端的故障。如果是這樣的話,它可能是更好地處理該故障為異常。該應用程序可以報告或記錄該異常,然后試圖通過調用另一個服務,持續或者(如果有一個可用的),或通過提供降級功能。關于如何檢測和處理持久故障的更多信息,請參閱斷路器格局。
### 何時使用這個模式
使用這種模式:
- 當一個應用程序可能會經歷短暫的故障,因為它與遠程服務進行交互,或訪問遠程資源。這些故障預計將是短暫的,并重復了以前沒有能夠成功的后續嘗試的請求。
### 這種模式可能不適合:
- 當故障很可能是持久的,因為這可能會影響應用程序的響應性。該應用程序可以簡單地是浪費時間和資源試圖重復請求是最有可能失敗。
- 對于處理故障是不因瞬時故障,如在應用程序的業務邏輯引起錯誤的內部的異常。
- 作為一種替代解決系統中的可擴展性問題。如果一個應用程序有頻繁的“忙”的故障,這是通常指示被訪問的服務或資源應相應加大。
### 例子
本實施例說明的重試模式的實現。該 OperationWithBasicRetryAsync 方法,如下所示,通過 TransientOperationAsync 方法異步調用外部服務(該方法的細節將特定于服務,并從樣本代碼被省略)。
~~~
private int retryCount = 3;
...
?
public async Task OperationWithBasicRetryAsync()
{
int currentRetry = 0;
?
for (; ;)
{
try
{
// Calling 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 is not a transient error
// or we should not retry re-throw 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();
}
}
?
// Async method that wraps a call to a remote service (details not shown).
private async Task TransientOperationAsync()
{
...
}
~~~
調用此方法的聲明被包裹在一個循環一個 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 may be as simple as checking the exception type, in other
// cases it may 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 may be transient.
return new[] {WebExceptionStatus.ConnectionClosed,
WebExceptionStatus.Timeout,
WebExceptionStatus.RequestCanceled }.
Contains(webException.Status);
}
?
// Additional exception checking logic goes here.
return false;
}
~~~
- 前言
- (一)—— 緩存預留模式
- (二)—— 斷路器模式
- (三)—— 補償交易模式
- (四)——消費者的競爭模式
- (五)——計算資源整合模式
- (六)——命令和查詢職責分離(CQRS)模式
- (七)——事件獲取模式
- (八)——外部配置存儲模式
- (九)—— 聯合身份模式
- (十)——守門員模式
- (十一)—— 健康端點監控模式
- (十二)—— 索引表模式
- (十三)——領導人選舉模式
- (十四)——實體化視圖模式
- (十五)—— 管道和過濾器模式
- (十六)——優先級隊列模式
- (十七)—— 基于隊列的負載均衡模式
- (十八)—— 重試模式
- (十九)——運行重構模式
- (二十)—— 調度程序代理管理者模式
- (二十一)——Sharding 分片模式
- (二十二)——靜態內容托管模式
- (二十三)——Throttling 節流模式
- (二十四)—— 仆人鍵模式