重試風暴反模式
當服務無法使用或忙碌時,讓用戶端過於頻繁重試連線可能會導致服務難以復原,而且可能會使得問題更糟。 永遠重試也沒有意義,因為要求通常只在定義的時段有效。
問題說明
在雲端中,服務有時會遇到問題,變得無法供用戶端使用,或必須對用戶端進行節流或速率限制。 雖然重試服務的失敗連線,對用戶端來說是個很好做法,但請它們務必不要過於頻繁重試或花費太長時間重試。 在短暫的一段時間內重試不太可能會成功,因為服務可能還未復原。 此外,如果在嘗試復原時進行大量連線嘗試,則可能會將服務置於更大的壓力下,而且重複的連線嘗試甚至可能會讓服務不知所措,使得基礎問題更糟。
下列範例說明用戶端連線到伺服器型 API 的案例。 如果要求未成功,則用戶端會立即重試,並持續一直重試。 這種行為通常會比這個範例中的行為更微妙,但也適用相同的準則。
public async Task<string> GetDataFromServer()
{
while(true)
{
var result = await httpClient.GetAsync(string.Format("http://{0}:8080/api/...", hostName));
if (result.IsSuccessStatusCode) break;
}
// ... Process result.
}
如何修正問題
用戶端應用程式應該遵循一些最佳做法,以避免造成重試風暴。
- 限制重試次數,並不要保持長時間重試。 儘管撰寫
while(true)
迴圈似乎很輕鬆,但幾乎肯定您實際上不想要長時間重試,因為導致要求起始的情況可能已變更。 在大部分的應用程式中,重試幾秒鐘或幾分鐘就足夠了。 - 重試之間的間隔。 如果服務無法使用,則立即重試不太可能會成功。 逐漸增加您在嘗試之間等候的時間量,例如使用指數輪詢策略。
- 正常地處理錯誤。 如果服務沒有回應,請考慮中止嘗試,並將錯誤傳回給您元件的使用者或呼叫者,是否有意義。 在設計您的應用程式時,請考慮這些失敗案例。
- 請考慮使用斷路器模式,其是為了協助避免重試風暴而特別設計的。
- 如果伺服器提供
retry-after
回應標頭,請確定在指定的時段過去之前不要嘗試重試。 - 與 Azure 服務進行通訊時,請使用官方 SDK。 這些 SDK 一般都有內建的重試原則和保護,以防範導致或促成重試風暴。 如果您要與沒有 SDK 的服務進行通訊,或與 SDK 未在其中正確地處理重試邏輯的服務進行通訊,請考慮使用 Polly (適用於 .NET) 或 retry (適用於 JavaScript) 之類的程式庫,以正確地處理重試邏輯,並避免自行撰寫程式碼。
- 如果您是在支援其的環境中執行,請使用服務網格 (或另一個抽象層) 來傳送輸出呼叫。 通常,這些工具 (例如 Dapr) 會支援重試原則,並會自動遵循最佳做法,例如在重複的嘗試之後停止。 這種方法表示您不必自行撰寫重試程式碼。
- 請考慮批次處理要求和使用要求共用 (如果可用的話)。 許多 SDK 會代表您處理要求批次和連線共用,這會減少您應用程式所進行的輸出連線嘗試總數,不過您仍然需要小心,不要過於頻繁地重試這些連線。
服務也應該保護自己,以防範重試風暴。
- 新增閘道層,讓您可以在事件期間關閉連線。 這是隔艙模式的範例。 Azure 為不同類型的解決方案 (包括 Front Door、應用程式閘道和 API 管理) 提供了許多不同的閘道服務。
- 在您的閘道節流要求,這確保您不會接受太多的要求,免得您的後端元件無法繼續操作。
- 如果您要進行節流,請送回
retry-after
標頭,以協助用戶端了解何時要重新嘗試其連線。
考量
- 用戶端應該考慮傳回的錯誤類型。 某些錯誤類型不會指出服務的失敗,而是指出用戶端傳送了無效要求。 例如,如果用戶端應用程式收到
400 Bad Request
錯誤回應,則重試相同的要求可能不會有所幫助,因為伺服器告訴您,您的要求無效。 - 用戶端應該考慮對重新嘗試連線有意義的時間長度。 您應該重試的時間長度取決於您的商務需求,以及您是否可以合理地將錯誤回傳給使用者或呼叫者。 在大部分的應用程式中,重試幾秒鐘或幾分鐘就足夠了。
如何偵測問題
從用戶端的觀點來看,這個問題的徵兆可能包含很長的回應或處理時間,以及指出重複嘗試以重試連線的遙測。
從服務的觀點來看,這個問題的徵兆可能會在短時間內包含來自某個用戶端的大量要求,或來自單一用戶端的大量要求,同時從中斷中復原。 徵兆也可能包括在復原服務時遇到困難,或在修復了錯誤之後服務的持續大量失敗。
範例診斷
下列各節說明在用戶端和服務端上偵測潛在重試風暴的一種方法。
從用戶端遙測中識別
Azure Application Insights 會記錄來自應用程式的遙測,並讓資料可供查詢和視覺化使用。 輸出連線會以相依性的形式追蹤,而且可以存取關於它們的相關資訊,並為它們製作圖表,以找出用戶端對相同服務提出大量輸出要求的時間。
下圖取自 Application Insights 入口網站內的 [計量] 索引標籤,並顯示依「遠端相依性名稱」分割的「相依性失敗」計量。 這說明了在短時間內,對相依性有大量 (超過 21,000) 的失敗連線嘗試。
從伺服器遙測中識別
伺服器應用程式可以從單一用戶端偵測大量的連線。 在下列範例中,Azure Front Door 會作為應用程式的閘道,並已設定為將所有要求記錄至 Log Analytics 工作區。
您可以針對 Log Analytics 執行下列 Kusto 查詢。 其會識別過去一天內已將大量要求傳送至應用程式的用戶端 IP 位址。
AzureDiagnostics
| where ResourceType == "FRONTDOORS" and Category == "FrontdoorAccessLog"
| where TimeGenerated > ago(1d)
| summarize count() by bin(TimeGenerated, 1h), clientIp_s
| order by count_ desc
在重試風暴期間執行此查詢會顯示來自單一 IP 位址的大量連線嘗試。