ASP.NET Core 微服務初探[2]:熔斷降級之Polly

来源:https://www.cnblogs.com/RainingNight/archive/2019/01/04/circuitbreaker-polly-in-asp-net-core.html
-Advertisement-
Play Games

當我們從單體架構遷移到微服務模式時,其中一個比較大的變化就是模塊(業務,服務等)間的調用方式。在以前,一個業務流程的執行在一個進程中就完成了,但是在微服務模式下可能會分散到2到10個,甚至更多的機器(微服務)上,這必然就要使用網路進行通信。而網路本身就是不可靠的,並隨著每個服務都根據自身的情況進行的 ...


當我們從單體架構遷移到微服務模式時,其中一個比較大的變化就是模塊(業務,服務等)間的調用方式。在以前,一個業務流程的執行在一個進程中就完成了,但是在微服務模式下可能會分散到2到10個,甚至更多的機器(微服務)上,這必然就要使用網路進行通信。而網路本身就是不可靠的,並隨著每個服務都根據自身的情況進行的動態擴容,以及機器漂移等等。可以說,在微服務中,網路連接緩慢,資源繁忙,暫時不可用,服務離線等異常情況已然變成了一種常態。因此我們必須要有一種機制來保證服務整體的穩定性,而本文要介紹的熔斷降級就是一種很好的應對方案。

服務熔斷

avalanche

在介紹熔斷之前,我們先來談談微服務中的雪崩效應。在微服務中,服務A調用服務B,服務B可能會調用服務C,服務C又可能調用服務D等等,這種情況非常常見。如果服務D出現不可用或響應時間過長,就會導致服務C原來越多的線程處於網路調用等待狀態,進而影響到服務B,再到服務A等,最後會耗盡整個系統的資源,導致整體的崩潰,這就是微服務中的“雪崩效應”。

而熔斷機制就是應對雪崩效應的一種鏈路保護機制。其實,對於熔斷這個詞我們並不陌生,在日常生活中經常會接觸到,比如:家用電力過載保護器,一旦電壓過高(發生漏電等),就會立即斷電,有些還會自動重試,以便在電壓正常時恢復供電。再比如:股票交易中,如果股票指數過高,也會採用熔斷機制,暫停股票的交易。同樣,在微服務中,熔斷機制就是對超時的服務進行短路,直接返回錯誤的響應信息,而不再浪費時間去等待不可用的服務,防止故障擴展到整個系統,併在檢測到該服務正常時恢復調用鏈路。

IM1L漏電斷路器

服務降級

當我們談到服務熔斷時,經常會提到服務降級,它可以看成是熔斷器的一部分,因為在熔斷器框架中,通常也會包含服務降級功能。

降級的目的是當某個服務提供者發生故障的時候,向調用方返回一個錯誤響應或者替代響應。從整體負荷來考慮,某個服務熔斷後,伺服器將不再被調用,此時客戶端可以自己準備一個本地的fallback回調,這樣,雖然服務水平下降,但總比直接掛掉的要好。比如:調用聯通介面伺服器發送簡訊失敗之後,改用移動簡訊伺服器發送,如果移動簡訊伺服器也失敗,則改用電信簡訊伺服器,如果還失敗,則返回“失敗”響應;再比如:在從推薦商品伺服器載入數據的時候,如果失敗,則改用從緩存中載入,如果緩存也載入失敗,則返回一些本地替代數據。

在某些情況下,我們也會採取主動降級的機制,比如雙十一活動等,由於資源的有限,我們也可以把少部分不重要的服務進行降級,以保證重要服務的穩定,待度過難關,再重新開啟。

Polly基本使用

在.Net Core中有一個被.Net基金會認可的庫Polly,它一種彈性和瞬態故障處理庫,可以用來簡化對服務熔斷降級的處理。主要包含以下功能:重試(Retry),斷路器(Circuit-breaker),超時檢測(Timeout),艙壁隔離(Bulkhead Isolation), 緩存(Cache),回退(FallBack)。

該項目作者現已成為.NET基金會一員,一直在不停的迭代和更新,項目地址: https://github.com/App-vNext/Polly

策略

在Polly中,有一個重要的概念:Policy,策略有“故障定義”和“故障恢復”兩部分組成。故障是指異常、非預期的返回值等情況,而動作則包括重試(Retry)、熔斷(Circuit-Breaker)、Fallback(降級)等。

故障定義

故障也可以說是觸發條件,它使用Handle<T>來定義,表示在什麼情況下,才對其進行處理(熔斷,降級,重試等)。

一個簡單的異常故障定義如下:

Policy.Handle<HttpRequestException>()

如上,表示當我們的代碼觸發HttpRequestException異常時,才進行處理。

我們也可以對異常的信息進行過濾:

Policy.Handle<SqlException>(ex => ex.Number == 1205)

如上,只有觸發SqlException異常,並且其異常號為1205的時候才進行處理。

如果我們希望同時處理多種異常,可以使用Or<T>來實現:

Policy.Handle<HttpRequestException>().Or<OperationCanceledException>()

Policy.Handle<SqlException>(ex => ex.Number == 1205).Or<ArgumentException>(ex => ex.ParamName == "example")

除此之外,我們還可以根據返回結果進行故障定義:

Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)

如上,當返回值為HttpResponseMessage,並且其StatusCodeNotFound時,才對其進行處理。更多用法參考:usage--fault-handling-policies

故障恢復

當定義了故障後,要考慮便是如何對故障進行恢復了,Polly中常用的有以下幾種恢復策略:

重試(Retry)策略

重試就是指Polly在調用失敗時捕獲我們指定的異常,並重新發起調用,如果重試成功,那麼對於調用者來說,就像沒有發生過異常一樣。在網路調用中經常出現瞬時故障,那麼重試機制就非常重要。

一個簡單的重試策略定義如下:

// 當發生HttpRequestException異常時,重試3次
var retryPolicy = Policy.Handle<HttpRequestException>().Retry(3);

有些情況下,如果故障恢復的太慢,我們重試的過快是沒有任何任何意義的,這時可以指定重試的時間間隔:

Policy.Handle<HttpRequestException>().WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1)));

如上,重試五次,並且重試時間指數級增加。

超時(Timeout)策略

超時是我們比較常見的,比如HttpClient就可以設置超時時間,如果在指定的時間內還沒有返回,就觸發一個TimeoutException異常,而Polly的超時機制與其類似,只不過超時時觸發的是一個TimeoutRejectedException異常。

// 如果30秒種內沒有執行完成,就觸發`TimeoutRejectedException`異常
Policy.TimeoutAsync(30);

// 設置超時回調
Policy.TimeoutAsync(30, onTimeout: (context, timespan, task) =>
{
    // do something 
});

由於超時策略本身就是拋出一個超時異常,所以不需要設置觸發條件。

回退(FallBack)策略

回退也稱服務降級,用來指定發生故障時的備用方案。

Policy<string>.Handle<HttpRequestException>().FallbackAsync("substitute data", (exception, context) =>
{
    // do something 
});

如上,如果觸發HttpRequestException異常時,就返回固定的substitute data

熔斷(Circuit-breaker)策略

斷路器用於在服務多次不可用時,快速響應失敗,保護系統故障免受過載。

Policy.Handle<HttpRequestException>().Or<TimeoutException>()
    .CircuitBreakerAsync(
        // 熔斷前允許出現幾次錯誤
        exceptionsAllowedBeforeBreaking: 3,
        // 熔斷時間
        durationOfBreak: TimeSpan.FromSeconds(100),
        // 熔斷時觸發
        onBreak: (ex, breakDelay) =>
        {
            // do something 
        },
        // 熔斷恢復時觸發
        onReset: () =>
        {
            // do something 
        },
        // 在熔斷時間到了之後觸發
        onHalfOpen: () =>
        {
            // do something 
        }
    );

如上,如果我們的業務代碼連續失敗3次,就觸發熔斷(onBreak),就不會再調用我們的業務代碼,而是直接拋出BrokenCircuitException異常。當熔斷時間(100s)過後,切換為HalfOpen狀態,觸發onHalfOpen事件,此時會再調用一次我們的業務代碼,如果調用成功,則觸發onReset事件,並解除熔斷,恢復初始狀態,否則立即切回熔斷狀態。

更多策略的用法查看:usage--general-resilience-policie

執行

在上面的示例中,我們熟悉了各種策略的定義,那麼接下來就是執行它。也就是使用Polly包裹我們的業務代碼,Polly會攔截業務代碼中的故障,並根據指定的策略進行恢復。

最簡單的策略執行方式如下:

var policy = /*策略定義*/;
var res = await policy.ExecuteAsync(/*業務代碼*/);

如果需要同時指定多個策略,可以使用Policy.Wrap來完成:

Policy.Wrap(retry, breaker, timeout).ExecuteAsync(/*業務代碼*/);

其實Warp本質就是多個策略的嵌套執行,使用如下寫法效果是一樣的:

fallback.Execute(() => waitAndRetry.Execute(() => breaker.Execute(action)));

關於Polly更詳細的用法可以查看Polly Github上的https://github.com/App-vNext/Polly/wiki,本文就不再過多介紹。

Polly熔斷降級實戰

場景:輪詢調用服務A和服務B,單次調用時間不得超過1s,調用失敗時自動切換到另外一個服務重試一次,如果都失敗,進行優雅的降級,返回模擬數據,併在2個服務都多次失敗後進行熔斷。

首先創建一個ASP.NET Core Console程式,命名為PollyDemo。

然後引入Polly的官方Nuge包:

dotnet add package Polly

在我們首先定義一個超時策略:

var timeoutPolicy = Policy.TimeoutAsync(1, (context, timespan, task) =>
{
    Console.WriteLine("It's Timeout, throw TimeoutRejectedException.");
    return Task.CompletedTask;
});

可以根據實際情況來設置超時時間,我這裡為了方便測試,就設置為1s。

然後定義重試策略:

var retryPolicy = Policy.Handle<HttpRequestException>().Or<TimeoutException>().Or<TimeoutRejectedException>()
    .WaitAndRetryAsync(
        retryCount: 2,
        sleepDurationProvider: retryAttempt =>
        {
            var waitSeconds = TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1));
            Console.WriteLine(DateTime.Now.ToString() + "-Retry:[" + retryAttempt + "], wait " + waitSeconds + "s!");
            return waitSeconds;
        });

再定義一個熔斷策略:

var circuitBreakerPolicy = Policy.Handle<HttpRequestException>().Or<TimeoutException>().Or<TimeoutRejectedException>()
    .CircuitBreakerAsync(
        // 熔斷前允許出現幾次錯誤
        exceptionsAllowedBeforeBreaking: 2,
        // 熔斷時間
        durationOfBreak: TimeSpan.FromSeconds(3),
        // 熔斷時觸發
        onBreak: (ex, breakDelay) =>
        {
            Console.WriteLine(DateTime.Now.ToString() + "Breaker->Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms! Exception: ", ex.Message);
        },
        // 熔斷恢復時觸發
        onReset: () =>
        {
            Console.WriteLine(DateTime.Now.ToString() + "Breaker->Call ok! Closed the circuit again.");
        },
        // 在熔斷時間到了之後觸發
        onHalfOpen: () =>
        {
            Console.WriteLine(DateTime.Now.ToString() + "Breaker->Half-open, next call is a trial.");
        }
    );

如上,連續錯誤2次就熔斷3秒。

最後,再定義一個回退策略:

var fallbackPolicy = Policy<string>.Handle<Exception>()
    .FallbackAsync(
        fallbackValue: "substitute data",
        onFallbackAsync: (exception, context) =>
        {
            Console.WriteLine("It's Fallback,  Exception->" + exception.Exception.Message + ", return substitute data.");
            return Task.CompletedTask;
        });

我們的業務代碼如下:

private List<string> services = new List<string> { "localhost:5001", "localhost:5002" };
private int serviceIndex = 0;
private HttpClient client = new HttpClient();

private Task<string> HttpInvokeAsync()
{
    if (serviceIndex >= services.Count)
    {
        serviceIndex = 0;
    }
    var service = services[serviceIndex++];
    Console.WriteLine(DateTime.Now.ToString() + "-Begin Http Invoke->" + service);
    return client.GetStringAsync("http://" + service + "/api/values");
}

這裡方便測試,直接寫死了兩個服務,對其輪詢調用,在生產環境中可以參考上一篇《服務發現之Consul》來實現服務發現和負載均衡。

現在,我們組合這些策略來調用我們的業務代碼:

for (int i = 0; i < 100; i++)
{
    Console.WriteLine(DateTime.Now.ToString() + "-Run[" + i + "]-----------------------------");
    var res = await fallbackPolicy.WrapAsync(Policy.WrapAsync(circuitBreakerPolicy, retryPolicy, timeoutPolicy)).ExecuteAsync(HttpInvokeAsync);
    Console.WriteLine(DateTime.Now.ToString() + "-Run[" + i + "]->Response" + ": Ok->" + res);
    await Task.Delay(1000);
    Console.WriteLine("--------------------------------------------------------------------------------------------------------------------");
}

如上,迴圈執行100次,策略的執行是非常簡單的,唯一需要註意的就是調用的順序:如上是依次從右到左進行調用,首先是進行超時的判斷,一旦超時就觸發TimeoutRejectedException異常,然後就進入到了重試策略中,如果重試了一次就成功了,那就直接返回,不再觸發其他策略,否則就進入到熔斷策略中:

breaking

如上圖,服務A(localhost:5001)和服務B(localhost:5001),都沒有啟動,所以會一直調用失敗,最後熔斷器開啟,並最終被降級策略攔截,返回substitute data

現在我們啟動服務A,可以看到服務會自動恢復,解除熔斷狀態:

reset

總結

本篇首先講解了一下微服務中熔斷、降級的基本概念,然後對.Net Core中的Polly框架做了一個基本介紹,最後基於Polly演示瞭如何在.NET Core中實現熔斷降級來提高服務質量。而熔斷本質上只是一個保護殼,在周圍出現異常的時候保全自身,從長遠來看,平時定期做好壓力測試才能防範於未然,降低觸發熔斷的次數。如果清楚的知道每個服務的承載量,並做好服務限流的控制,就能將“高壓”下觸發熔斷的概率降到最低了。那下一篇就來介紹一下速率限制(Rate Limiting),敬請期待!

附本篇示例源碼地址:https://github.com/RainingNight/AspNetCoreSample/tree/master/src/Microservice/CircuitBreaker/PollyDemo

參考資料


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 private static string IsOpenSendMail = ConfigurationManager.AppSettings["IsOpenSendMail"]; //是否啟用異常消息發送郵箱功能 2 private static string SendUser=Configu... ...
  • 介紹 微服務中有關鍵的幾項技術,其中網關和服務服務發現,服務註冊相輔相成。 首先解釋幾個本次教程中需要的術語 網關 Gateway(API GW / API 網關),顧名思義,是企業 IT 在系統邊界上提供給外部訪問內部介面服務的統一入口,簡化了外部由於多服務協同完成任務時的繁瑣配置。網關組件有Ko ...
  • 第1節. 關鍵字 馳騁工作流引擎 流程快速開發平臺 workflow ccflow jflow 第1節. 流程引擎表結構的設計 流程引擎表是流程引擎控制流程運轉的數據存儲表,是整個流程引擎的核心表。理解表結構,掌握狀態欄位,相關欄位變化的規律,就掌握的整個流程引擎運轉的規律了。 1.1.1: 流程引... ...
  • Console.Write();和Console.WriteLine();區別詳解: Console.Write();,專業:將指定的字元串值寫入標準輸出流。通俗:控制台輸出,不換行。 示例代碼: 1 using System; 2 using System.Collections.Generic; ...
  • 圖片上傳和展示是互聯網應用中比較常見的一個功能,最近做的一個門戶網站項目就有多個需要上傳圖片的功能模塊。關於這部分內容,本來功能不複雜,但後面做起來卻還是出現了一些波折。因為缺乏經驗,對幾種圖片上傳的方法以及使用範圍和優缺點都不太瞭解,導致在做相關功能時也確實走了一些彎路。 起初我用的是SaveAs ...
  • 收集的Emgucv的整理書籍資料和Emgucv動態調試器:下載地址 本文代碼下載:下載 ...
  • 1. 引用System.Speech 2. 通過SpeechSynthesizer類朗讀文本 new SpeechSynthesizer().SpeakAsync("我們都是好孩子We're good kids.") 3. Speck vs SpeckAsync函數 PlayAsync--異常播放, ...
  • 認識ASP.NET CoreASP.NET Core是一個跨平臺,高性能,開源的框架,用於構建現代,基於雲的網路應用程式,使用ASP.NET Core可以實現:開發web應用,服務,IoT應用和移動端的後臺api。可以使用自己擅長的開發工具實現跨平臺開發,包括Windows,macOS和Linux。... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...