1 前言之服務雪崩
在我們實施微服務之后,服務間的調用變得異常頻繁,多個服務之前可能存在互相依賴的關系,當某個服務出現故障或者是因為服務間的網絡出現故障,導致服務調用的失敗,進而影響到某個業務服務處理失敗,服務依賴的故障可能導致級聯崩潰,如一個微服務不可用拖垮整個系統。【服務雪崩】
服務雪崩通常遵循 “從局部故障到全局崩潰” 的遞進路徑,可拆解為以下步驟:
- 初始故障
某個基礎服務(如數據庫、緩存、第三方 API)因過載、網絡中斷或 bug 出現響應延遲或失敗。
例:支付服務因數據庫連接池耗盡,處理請求的時間從 100ms 增至 5s。 - 請求堆積
依賴該故障服務的上游服務(如訂單服務)因等待響應,線程 / 連接被長時間占用,無法釋放資源。
例:訂單服務調用支付服務時,每個請求都需等待 5s,而線程池容量有限(如 200 線程),很快所有線程被占滿。 - 級聯阻塞
上游服務因資源耗盡,無法處理新請求,導致依賴它的更上游服務(如用戶服務)也出現資源耗盡。
例:用戶服務調用訂單服務時,因訂單服務無響應,用戶服務的線程也被占滿,無法處理用戶登錄請求。 - 全局崩潰
故障像多米諾骨牌一樣擴散,最終整個系統的核心功能(如下單、支付、登錄)全部失效。
服務雪崩的本質是 “故障的無限制擴散”. 就像是蝴蝶效應最后導致美國得克薩斯州的一場龍卷風
2 如何解決服務雪崩
針對雪崩的形成機制,需從 “限制資源消耗”“隔離故障”“快速失敗” 三個維度設計防護策略:
- 熔斷機制(Circuit Breaker)
- 原理:當某個服務的失敗率超過閾值(如 50% 失敗),暫時 “斷開” 對它的調用,直接返回降級結果,避免資源浪費。
- 效果:防止故障服務持續消耗上游資源,給故障服務恢復的時間窗口。
- 限流機制(Rate Limiting)
- 原理:限制單位時間內對某個服務的請求量(如每秒 1000 次),防止瞬時流量沖垮服務。
- 場景:針對下游服務的最大承載能力,提前設置流量閾值。
- 隔離機制(Isolation)
- 原理:為不同服務的調用分配獨立的資源池(線程池、連接池),避免一個服務的故障耗盡全局資源。
- 例:訂單服務調用支付服務時使用單獨的線程池(20 線程),調用庫存服務時使用另一個線程池(30 線程),即使支付服務故障,也不會影響庫存服務的調用。
- 超時控制(Timeout)
- 原理:為服務調用設置明確的超時時間(如 2s),超過時間直接終止請求,避免線程被無限期占用。
- 關鍵:超時時間需根據下游服務的正常響應時間合理設置(通常略高于 99% 請求的處理時間)。
- 降級策略(Fallback)
- 原理:當服務調用失敗時,返回預設的兜底結果(而非直接報錯),保證核心流程可用。
- 例:推薦服務故障時,返回默認熱門商品列表;支付服務超時后,返回 “支付中,請稍后查詢”。
3 什么是Polly
Polly 是 .NET 生態中一款強大的 彈性和瞬態故障處理庫,主要用于處理分布式系統中常見的網絡故障、超時、資源限流等問題,通過預定義的策略(如重試、熔斷、超時等)提高應用程序的穩定性和容錯能力。從而增強服務的可用性
3.1 超時策略
超時策略可防止因長時間阻塞導致的資源耗盡或級聯故障.
在 Polly 中,TimeoutStrategy 是控制超時行為的核心枚舉,
定義了兩種不同的超時處理機制:樂觀超時(Optimistic)和悲觀超時(Pessimistic)
Optimistic使用CancellationToken取消服務,默認是使用樂觀超時的處理機制
Pessimistic 不需要使用CancellationToken,強制終端業務邏輯,這種情形可能會導致相關聯的資源沒有關閉。需要對這部分邏輯做處理
策略類型 觸發條件 依賴操作協作 拋出的異常類型 樂觀超時 超時 + 操作響應取消 是 OperationCanceledException
悲觀超時 超時(強制觸發) 否 TimeoutRejectedException
/// <summary>/// Polly的超時策略【使用樂觀處理機制】/// </summary>/// <param name="url"></param>/// <returns></returns>[HttpGet]public async Task<IActionResult> TimeOutOptimisticPolicy(string url) {// 示例:設置 5 秒悲觀超時 超時后將執行回調函數// 超時策略可防止因長時間阻塞導致的資源耗盡或級聯故障// 在 Polly 中,TimeoutStrategy 是控制超時行為的核心枚舉,// 定義了兩種不同的超時處理機制:樂觀超時(Optimistic)和悲觀超時(Pessimistic)// Optimistic使用CancellationToken取消服務,默認是使用樂觀超時的處理機制// Pessimistic不需要使用CancellationToken取消服務// onTimeoutAsync回調函數是在超時發生時執行的附加操作,但它不會改變 ExecuteAsync 的返回值。// 這個回調主要用于日志記錄、監控或其他副作用操作,而不是直接返回 HTTP 響應。using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));var token = cts.Token;var timeOutPolicy = Policy.TimeoutAsync<IActionResult>(timeout: TimeSpan.FromSeconds(3),timeoutStrategy: TimeoutStrategy.Optimistic,onTimeoutAsync: (context, timespan, task) =>{Console.WriteLine($"操作超時:{timespan.TotalSeconds}秒");return Task.CompletedTask;});return await timeOutPolicy. ExecuteAsync(async token =>{try{using (HttpClient client = new HttpClient()){HttpResponseMessage message = await client.GetAsync(url, token);//序列化響應結果string resContent = await message.Content.ReadAsStringAsync();return new OkObjectResult(resContent);}}//任務由于超時異常被取消 ,在任務內部拋出 TaskCanceledException 異常catch (TaskCanceledException e) {return new ObjectResult(new ReturnMessageModel<string>{Code = "408",Message = "操作超時",}){ StatusCode = 408 };}catch (Exception e){return new ObjectResult(new ReturnMessageModel<string>{Code = "500",Message = $"服務錯誤:{e.Message}" + e.GetType().Name,Status = (int)HttpStatusCode.InternalServerError}){ StatusCode = 500 };}}, token);}/// <summary>/// Polly的超時策略【使用悲觀處理機制】/// </summary>/// <param name="url"></param>/// <returns></returns>[HttpGet]public async Task<IActionResult> TimeOutPessimisticPolicy(string url){var timeOutPolicy = Policy.TimeoutAsync<IActionResult>(timeout: TimeSpan.FromSeconds(3),timeoutStrategy: TimeoutStrategy.Pessimistic,onTimeoutAsync: (context, timespan, task) =>{Console.WriteLine($"操作超時:{timespan.TotalSeconds}秒");return Task.CompletedTask;});try{return await timeOutPolicy.ExecuteAsync(async () =>{try{using (HttpClient client = new HttpClient()){HttpResponseMessage message = await client.GetAsync(url);//序列化響應結果string resContent = await message.Content.ReadAsStringAsync();return new OkObjectResult(resContent);}}catch (Exception e){return new ObjectResult(new ReturnMessageModel<string>{Code = "500",Message = $"服務錯誤:{e.Message}" + e.GetType().Name,Status = (int)HttpStatusCode.InternalServerError}){ StatusCode = 500 };}});}catch (TimeoutRejectedException e) {// 服務超時。return new ObjectResult(new ReturnMessageModel<string>{Code = "408",Message = "操作超時",}){ StatusCode = 408 };}}
3.2 重試策略
在分布式系統中,網絡請求可能會因為臨時故障(如網絡抖動、服務暫時不可用)而失敗。Polly 的重試策略(Retry Policy)是處理這類問題的有效工具,它可以在請求失敗時自動重試,提高系統的穩定性和容錯能力。
固定次數重試策略
/// <summary>/// 固定次數重試策略/// </summary>/// <param name="url"></param>/// <returns></returns>[HttpGet]public async Task<IActionResult> RetryPolicy(string url) {// 重試策略,對所有捕獲的異常進行重試次數策略,當重試次數<= 設定值時,均會進入回調函數邏輯var policy = Policy.Handle<Exception>().RetryAsync(3, (exception, retryCount) => {Console.WriteLine($"重試第 {retryCount} 次: {exception.Message}");});try{return await policy.ExecuteAsync(async () =>{using (HttpClient client = new HttpClient()){HttpResponseMessage message = await client.GetAsync(url);//序列化響應結果string resContent = await message.Content.ReadAsStringAsync();return new OkObjectResult(resContent);}});}catch (Exception e) {return new ObjectResult(new ReturnMessageModel<string>{Code = "500",Message = $"服務錯誤:{e.Message}" + e.GetType().Name,Status = (int)HttpStatusCode.InternalServerError}){ StatusCode = 500 };}}
3.3 降級策略
服務降級(Service Degradation 是一種應對系統過載或依賴服務故障的彈性策略,通過暫時犧牲部分非核心功能或服務質量,確保系統核心功能的可用性和穩定性.一般是碰到異常時會給一個默認回調的形式去代替原有的服務
/// <summary>/// 模擬從緩存中獲得暫時的響應數據/// </summary>/// <returns></returns>private async Task<HttpResponseMessage> GetDataFromRedis() {HttpResponseMessage httpResponse = new HttpResponseMessage();httpResponse.StatusCode = HttpStatusCode.OK;var resdata=new ReturnMessageModel<bool>("0",0,"從緩存中獲得數據",true);string json = JsonConvert.SerializeObject(resdata);httpResponse.Content = new StringContent(json, Encoding.UTF8, "application/json");return httpResponse;}/// <summary>/// 服務降級策略【降級策略常常搭配其他策略一起使用】/// </summary>/// <param name="url"></param>/// <returns></returns>[HttpGet]public async Task<ReturnMessageModel<string>> DegradationPolicy(string url) {// 定義重試策略var retryPolicy = Policy<HttpResponseMessage>.Handle<Exception>().RetryAsync(3,onRetryAsync: (ex, times) => {Debug.WriteLine($"當前重試次數為 {times}");return Task.CompletedTask;});// 定義降級策略var fallbackPolicy = Policy<HttpResponseMessage>.Handle<Exception>().FallbackAsync(async (cancellaction) =>{// 該回調函數中執行降級的邏輯,比如說從緩存中獲取邏輯Debug.WriteLine("進行了服務降級策略");return await GetDataFromRedis();});// Polly 處理策略,先重試,后降級// Policy.WrapAsync 包含多種執行策略// 在Polly中,策略的包裹順序非常重要,因為策略的執行順序是從最外層策略開始,逐層向內執行。// 當我們使用PolicyWrap時,通過Policy.WrapAsync方法組合策略,會形成一個策略管道,請求首先進入最先包裹的策略(最外層),然后依次向內傳遞,直到執行用戶提供的原始委托。var policy = Policy.WrapAsync(fallbackPolicy,retryPolicy);// 使用降級策略去處理任務HttpResponseMessage res= await policy.ExecuteAsync(async() =>{using (HttpClient client = new HttpClient()){HttpResponseMessage message = await client.GetAsync(url);return message;}});string resContent = await res.Content.ReadAsStringAsync();return new ReturnMessageModel<string>(resContent);}
3.4 熔斷策略
Polly 的熔斷策略(Circuit Breaker)用于在系統檢測到持續故障時,自動斷開(熔斷)對特定服務的請求,防止系統資源被耗盡并快速失敗。當熔斷器處于開啟狀態時,所有請求會立即被拒絕(拋出
BrokenCircuitException
),直到經過設定的恢復期后,熔斷器會進入半開狀態,允許部分請求嘗試恢復服務。如果這些請求成功,則關閉熔斷器;否則,繼續保持開啟。熔斷器包含三個狀態
Closed(閉合):正常狀態,操作允許執行。
Open(斷開):熔斷狀態,所有操作被快速失敗,不再嘗試執行。
Half-Open(半開):熔斷器嘗試恢復,允許少量操作執行以測試依賴服務是否恢復。
Polly提供兩種熔斷器策略:
基于連續失敗次數的熔斷器(Basic Circuit Breaker)
基于高級失敗率(滑動窗口)的熔斷器(Advanced Circuit Breaker)
熔斷器狀態需要跨多個調用共享,因此通常將熔斷器策略實例聲明為靜態或通過依賴注入容器注入(單例生命周期)。
BasicCircuitBreaker代碼演示
// 靜態熔斷器實例(狀態持久化)private static readonly AsyncCircuitBreakerPolicy _circuitBreaker = Policy.Handle<Exception>().CircuitBreakerAsync(exceptionsAllowedBeforeBreaking: 4,durationOfBreak: TimeSpan.FromSeconds(30),onBreak: (ex, breakDelay) =>{Debug.WriteLine($"熔斷開啟!持續 {breakDelay.TotalSeconds} 秒。");},onReset: () =>{Debug.WriteLine("熔斷關閉,恢復正常請求。");},onHalfOpen: () =>{Debug.WriteLine("熔斷器進入半開狀態,嘗試恢復。");});
/// <summary>/// 熔斷策略[基于連續失敗次數的熔斷器]/// </summary>/// <param name="url">請求API</param>/// <returns></returns>[HttpGet]public async Task<ReturnMessageModel<string>> BasicCircuitBreakPolicy(string url) {// 重試策略var retryPolicy = Policy.Handle<Exception>().RetryAsync(5, (ex, qty) => {Thread.Sleep(100);Debug.WriteLine($"當前重試次數為 {qty} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");return Task.CompletedTask;});// 先重試 再熔斷 最后降級服務var fallbackPolicy = Policy<string>.Handle<Exception>().FallbackAsync("先熔斷降級策略結果", onFallbackAsync: _ => {Debug.WriteLine($"降級策略將要執行 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");return Task.CompletedTask;}).WrapAsync(_circuitBreaker.WrapAsync(retryPolicy));string res = await fallbackPolicy.ExecuteAsync(async() =>{using (HttpClient client = new HttpClient()){HttpResponseMessage message = await client.GetAsync(url);string messageStr=await message.Content.ReadAsStringAsync();return messageStr;}});return new ReturnMessageModel<string>(res); }
Advanced Circuit Breaker代碼演示,將上述的基礎異常次數_circuitBreaker熔斷器換成_advancedCircuitBreaker
private static readonly AsyncCircuitBreakerPolicy _advancedCircuitBreaker = Policy.Handle<Exception>().AdvancedCircuitBreakerAsync(failureThreshold: 0.5, // 失敗率閾值(50%)samplingDuration: TimeSpan.FromSeconds(10), // 采樣時間窗口(10秒)minimumThroughput: 5, // 最小吞吐量(10秒內至少8個請求)durationOfBreak: TimeSpan.FromSeconds(30), // 熔斷持續時間onBreak: (ex, breakDelay) =>{Debug.WriteLine($"熔斷開啟!持續 {breakDelay.TotalSeconds} 秒。");},onReset: () =>{Debug.WriteLine("熔斷關閉,恢復正常請求。");},onHalfOpen: () =>{Debug.WriteLine("熔斷器進入半開狀態,嘗試恢復。");});