重新整理 .net core 實踐篇————熔斷與限流[三十五]

敖毛毛發表於2021-07-03

前言

簡單整理一下熔斷與限流,跟上一節息息相關。

正文

polly 的策略型別分為兩類:

  1. 被動策略(異常處理、結果處理)

  2. 主動策略(超時處理、斷路器、艙壁隔離、快取)

熔斷和限流通過下面主動策略來實現:

  1. 降級響應

  2. 失敗重試

  3. 斷路器

  4. 艙壁隔離

Policy 型別 狀態 說明
CircuitBreaker(斷路器) 有狀態 共享失敗率,以決定是否熔斷
Bulkhead(艙壁隔離) 有狀態 共享容量使用情況,以決定是否執行動作
Cache(快取) 有狀態 共享快取的物件,以決定是否命中
其他策略 無狀態

先來看一下熔斷,什麼是熔斷呢?

熔斷就是讓我們的上游伺服器一段時間內對下游伺服器不進行呼叫。

這裡解釋一下上游伺服器和下游伺服器,比如說A呼叫B,那麼A就是上游伺服器,B就是下游伺服器。

那麼為什麼要熔斷呢?比如說A呼叫B,現在A呼叫B 10次有8次是錯誤的,那麼這個時候就要想一件事,程式碼沒有變過,那麼肯定是量變成了質變。

這時候B之所以不可用,那麼是因為請求太多了,處理不過來(比如記憶體升高了,io 99%了等)。

那麼這個時候A就不進行呼叫了,隔一段時間後再進行呼叫。也就是A對B的這條線進行了熔斷處理。

services.AddHttpClient("GreeterClient").AddPolicyHandler(Policy<HttpResponseMessage>
	.Handle<HttpRequestException>().CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 10,
		durationOfBreak: TimeSpan.FromSeconds(10), 
		onBreak: (r, t) =>
		{
			// 熔斷的時候處理事件
		},
		onReset: () =>
		{
			// 恢復的時候的處理
		},onHalfOpen: () =>
		{
			// 恢復之前進行處理
		}));

CircuitBreakerAsync 表示斷路器,這個用來實現熔斷的。

handledEventsAllowedBeforeBreaking 表示失敗10次,進行熔斷。

durationOfBreak 熔斷的事件

其他幾個事件上面做了備註。

其實上面這種不常用,因為限制比較死,比如說10次就熔斷。

一般都是百分比來計算的。

services.AddHttpClient("GreeterClient").AddPolicyHandler(Policy<HttpResponseMessage>
	.Handle<HttpRequestException>().AdvancedCircuitBreakerAsync(
		failureThreshold:0.8,
		samplingDuration:TimeSpan.FromSeconds(10),
		minimumThroughput:100,
		durationOfBreak: TimeSpan.FromSeconds(10),
		onBreak: (r, t) =>
		{
			// 熔斷的時候處理事件
		},
		onReset: () =>
		{
			// 恢復的時候的處理
		}, onHalfOpen: () =>
		{
			// 恢復之前進行處理
		}));

failureThreshold 表示失敗的比例

samplingDuration 表示失敗的時間

failureThreshold 和 samplingDuration一般是組合起來用的,表示是10秒內失敗次數要有80%就會熔斷。

minimumThroughput 表示10秒類必須有100個請求才會出根據其他的條件進行熔斷判斷。

上面就是熔斷了,那麼什麼是服務降級呢?

網上的一段話是這樣的:服務降級是指 當伺服器壓力劇增的情況下,根據實際業務情況及流量,對一些服務和頁面有策略的不處理或換種簡單的方式處理,從而釋放伺服器資源以保證核心業務正常運作或高效運作。

這段話的感覺好像是關閉某些服務一樣,而且比較繞。

個人理解是服務降級是指降低了原有的服務體驗,都屬於服務降級。

熔斷其實也是一種服務降級,但是單純的熔斷就降級的有點厲害了。

比如我去買東西,然後店直接關門了,服務體驗下降了,體驗降得比較厲害。

但是如果去買東西,店沒有關閉,而是有一排服務員,告訴你現在因為供銷商沒到貨買不到了,這體驗是不是好點,這也是服務降級。

那麼就看下第二種情況的服務降級怎麼實現吧。

var breakPolicy = Policy<HttpResponseMessage>
   .Handle<HttpRequestException>().AdvancedCircuitBreakerAsync(
	   failureThreshold: 0.8,
	   samplingDuration: TimeSpan.FromSeconds(10),
	   minimumThroughput: 100,
	   durationOfBreak: TimeSpan.FromSeconds(10),
	   onBreak: (r, t) =>
	   {
		   // 熔斷的時候處理事件
	   },
	   onReset: () =>
	   {
		   // 恢復的時候的處理
	   }, onHalfOpen: () =>
	   {
		   // 恢復之前進行處理
	   });

var message = new HttpResponseMessage()
{
	Content = new StringContent("不要慌,老闆沒有跑路,只是和老婆的妹妹出去了,要等一下!")
};

var fallback = Policy<HttpResponseMessage>.Handle<BrokenCircuitException>().FallbackAsync(message);

var fallbackBreak = Policy.WrapAsync(fallback, breakPolicy);

services.AddHttpClient("GreeterClient").AddPolicyHandler(fallbackBreak);

上面程式碼就是當熔斷後,過來的請求會有BrokenCircuitException異常,那麼捕獲到BrokenCircuitException異常,那麼就告訴使用者店沒有倒閉,等一下就好。

這種就是比較優雅的降級。

上面這種var fallbackBreak = Policy.WrapAsync(fallback, breakPolicy); 就是將幾個policy組合在一起,然後給某個或者某些HttpClient 增加該組合策略,比如Policy.WrapAsync(fallback, breakPolicy,xxx,yyy)等。

接下來解釋一下限流。

為什麼要限流呢? 如果沒有限流其實熔斷的意義是不大的。

為什麼這麼說呢? 比如說公司有1百萬請求,然後這個時候熔斷了,但是這100w請求還在啊,只有恢復服務,伺服器又要進行熔斷,一下子就瞬間爆炸。

var bulk = Policy.BulkheadAsync<HttpResponseMessage>(
	maxParallelization:30,
	maxQueuingActions:20,
	onBulkheadRejectedAsync:context=>Task.CompletedTask);

var message = new HttpResponseMessage()
{
	Content = new StringContent("你沒有搶到號碼,下次再來。")
};

var fallbackBulk = Policy<HttpResponseMessage>.Handle<BulkheadRejectedException>().FallbackAsync(message);

var fallbackBreak = Policy.WrapAsync(bulk, fallbackBulk);

上面這個就是限流了。

maxParallelization 表示可以併發處理30個請求,maxQueuingActions表示如果併發處於30,那麼會加入到佇列中,佇列中最大能存20個。

如果佇列中也存不下,那麼就會丟擲BulkheadRejectedException這種拒絕異常,一但出現異常,第二個策略就捕獲到了,然後給出一些友好的提示。

下一節,閘道器。

相關文章