如先前所述,您應該處理可能需要一段時間才能復原的錯誤,就像您嘗試連線到遠端服務或資源時可能發生的一樣。 處理這種類型的錯誤可以改善應用程式的穩定性和復原能力。
在分散式環境中,遠端資源和服務的呼叫可能會因為暫時性錯誤而失敗,例如網路連線緩慢和逾時,或是資源回應緩慢或暫時無法使用。 這些錯誤通常會在短時間內自行修正,而強固的雲端應用程式應該使用類似「重試模式」的策略來處理這些錯誤。
不過,也可能有錯誤是由於非預期的事件而需要更長的時間才能修正的情況。 這些錯誤的嚴重性可能從失去部分連線到服務完全失敗。 在這些情況下,應用程式可能會毫無意義地持續重試不太可能成功的作業。
相反地,應用程式應該編碼為接受作業失敗,並據以處理失敗。
不小心使用 Http 重試可能會在您自己的軟體中引發阻斷服務(DoS)攻擊。 當微服務失敗或執行緩慢時,多個用戶端可能會重複重試失敗的要求。 這會造成針對故障服務的流量急劇增長,從而帶來嚴重的風險。
因此,您需要某種防禦屏障,讓過多的要求在不值得持續嘗試時停止。 這種防禦屏障正是斷路器。
斷路器模式的用途與「重試模式」不同。 「重試模式」可讓應用程式在預期作業最終成功的情況下重試作業。 斷路器模式可防止應用程式執行可能失敗的作業。 應用程式可以結合這兩種模式。 不過,重試邏輯應該對斷路器傳回的任何例外狀況敏感,如果斷路器指出錯誤不是暫時性的,則應該放棄重試嘗試。
使用 IHttpClientFactory
和 Polly 實現斷路器模式
如同在實作重試機制時,採用斷路器模式的建議方法是利用已被證實的 .NET 程式庫,例如 Polly 及其與 IHttpClientFactory
的原生整合。
將斷路器策略新增至 IHttpClientFactory
傳出中間件流水線非常簡單,就像在使用 IHttpClientFactory
時,把少量新增的程式碼加入到您已有的程式碼中一樣簡單。
這裡唯一新增至用於 HTTP 呼叫重試的程式碼是將 Circuit Breaker 原則加至要使用的原則清單的程式代碼,如下列逐步程式代碼所示。
// Program.cs
var retryPolicy = GetRetryPolicy();
var circuitBreakerPolicy = GetCircuitBreakerPolicy();
builder.Services.AddHttpClient<IBasketService, BasketService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5)) // Sample: default lifetime is 2 minutes
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retryPolicy)
.AddPolicyHandler(circuitBreakerPolicy);
AddPolicyHandler()
方法負責將策略添加到您將使用的 HttpClient
物件。 在此情況下,它會為斷路器新增 Polly 原則。
若要有更模組化的方法,斷路器原則會定義在稱為 GetCircuitBreakerPolicy()
的個別方法中,如下列程式代碼所示:
// also in Program.cs
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
}
在上述程式代碼範例中,會設定斷路器原則,以便在重試 Http 要求時發生五個連續錯誤時中斷或開啟線路。 發生這種情況時,電路會中斷 30 秒:在這段期間,斷路器會使呼叫立即失敗,而不會真正接通。 原則會自動將 相關的例外狀況和 HTTP 狀態代碼 解譯為錯誤。
斷路器也應該用來在特定資源出現問題時,將請求重新導向至後備基礎設施。這些資源是部署在與執行 HTTP 呼叫的客戶端應用程式或服務不同的環境中。 如此一來,如果數據中心發生中斷,只會影響後端微服務,但不會影響用戶端應用程式,用戶端應用程式可以重新導向至後援服務。 Polly 正在規劃新的政策,以自動化此 故障轉移政策 情境。
所有這些功能都適用於您從 .NET 程式代碼中管理故障轉移的情況,而不是讓 Azure 自動為您管理故障轉移,且具有位置透明度。
從使用方式的觀點來看,使用 HttpClient 時,這裡不需要新增任何項目,因為程式代碼與搭配 HttpClient
使用 IHttpClientFactory
時相同,如前幾節所示。
在 eShopOnContainers 中測試 HTTP 重試機制和熔斷器機制
每當您在 Docker 主機中啟動 eShopOnContainers 解決方案時,都必須啟動多個容器。 某些容器的啟動和初始化速度較慢,例如 SQL Server 容器。 當您第一次將 eShopOnContainers 應用程式部署到 Docker 時,尤其如此,因為它需要設定映像和資料庫。 某些容器的啟動速度比其他容器慢,可能會導致其餘的服務最初擲回 HTTP 例外狀況,即使您在 docker-compose 層級設定容器之間的相依性,如前幾節所述。 容器之間透過 docker-compose 建立的相依性只在程序層級。 容器的進入點進程可能已啟動,但 SQL Server 可能尚未準備好進行查詢。 結果可能是一連串的錯誤,而應用程式在嘗試取用該特定容器時可能會取得例外狀況。
當應用程式部署至雲端時,您可能也會在啟動時看到這種錯誤。 在此情況下,協調器可能會在平衡叢集節點的容器數目時,將容器從一個節點或 VM 移至另一個節點(也就是啟動新的實例)。
當啟動所有容器時,'eShopOnContainers' 解決這些問題的方式是使用稍早說明的重試模式。
在 eShopOnContainers 中測試斷路器
有幾種方式可以中斷/開啟線路,並使用 eShopOnContainers 進行測試。
其中一個選項是將斷路器原則中允許的重試次數降低為1,並將整個解決方案重新部署至Docker。 只要重試一次,就很有可能 HTTP 要求在部署期間失敗、斷路器會開啟,而且您會收到錯誤。
另一個選項是使用 在購物籃 微服務中實作的自定義中間件。 啟用此中間件時,它會攔截所有 HTTP 要求,並傳回狀態代碼 500。 您可以對失敗的 URI 提出 GET 要求來啟用中間件,如下所示:
GET http://localhost:5103/failing
此要求會傳回中間件的目前狀態。 如果中間件已啟用,要求會傳回狀態代碼 500。 如果中間件已停用,則沒有回應。GET http://localhost:5103/failing?enable
此要求會啟用中間件。GET http://localhost:5103/failing?disable
此要求會停用中間件。
例如,一旦應用程式執行,您可以在任何瀏覽器中使用下列 URI 提出要求,以啟用中間件。 請注意,訂購微服務使用埠 5103。
http://localhost:5103/failing?enable
接著,您可以使用 URI http://localhost:5103/failing
檢查狀態,如圖 8-5 所示。
圖 8-5。 檢查「失敗」ASP.NET 中間件的狀態 – 在此情況下,已停用。
此時,每當您呼叫購物籃微服務時,它會響應狀態代碼 500。
執行中間件之後,您可以嘗試從MVC Web 應用程式進行訂單。 由於要求失敗,線路將會開啟。
在下列範例中,您可以看到MVC Web 應用程式在邏輯中有 catch 區塊來下訂單。 如果程式代碼攔截到開放式線路例外狀況,則會向用戶顯示易記訊息,告知他們等候。
public class CartController : Controller
{
//…
public async Task<IActionResult> Index()
{
try
{
var user = _appUserParser.Parse(HttpContext.User);
//Http requests using the Typed Client (Service Agent)
var vm = await _basketSvc.GetBasket(user);
return View(vm);
}
catch (BrokenCircuitException)
{
// Catches error when Basket.api is in circuit-opened mode
HandleBrokenCircuitException();
}
return View();
}
private void HandleBrokenCircuitException()
{
TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business message due to Circuit-Breaker)";
}
}
以下是摘要。 重試原則會嘗試數次進行 HTTP 要求並取得 HTTP 錯誤。 當重試次數達到斷路器原則設定的最大數目時(在此案例中為5),應用程式會擲回 BrokenCircuitException。 結果是友善的訊息,如圖 8-6 所示。
圖 8-6。 斷路器將錯誤傳回 UI
您可以針對何時開啟/中斷線路實作不同的邏輯。 或者,如果有後援數據中心或備援後端系統,您可以針對不同的後端微服務嘗試 HTTP 要求。
最後,CircuitBreakerPolicy
的另一種可能性是使用 Isolate
(強制電路保持打開)和 Reset
(使電路再次關閉)。 這些可用來建置實用性 HTTP 端點,以直接在策略上執行隔離模式和重設模式。 這類 HTTP 端點也可以在生產環境中適當地使用,以便暫時隔離下游系統,例如當您想要升級它時。 或者,它可以手動地斷路,以保護您懷疑有故障的下游系統。