當前位置:
首頁 > 最新 > 開源服務容錯處理庫Polly使用文檔

開源服務容錯處理庫Polly使用文檔

在進入SOA之後,我們的代碼從本地方法調用變成了跨機器的通信。任何一個新技術的引入都會為我們解決特定的問題,都會帶來一些新的問題。比如網路故障、依賴服務崩潰、超時、伺服器內存與CPU等其它問題。正是因為這些問題無法避免,所以我們在進行系統設計、特別是進行分散式系統設計的時候以「Design For Failure」(為失敗而設計)為指導原則。把一些邊緣場景以及服務之間的調用發生的異常和超時當成一定會發生的情況來預先進行處理。

Design For Failure

1.一個依賴服務的故障不會嚴重破壞用戶的體驗。

2. 系統能自動或半自動處理故障,具備自我恢復能力。

以下是一些經驗的服務容錯模式

超時與重試(Timeout and Retry)

限流(Rate Limiting)

熔斷器(Circuit Breaker)

艙壁隔離(Bulkhead Isolation)

回退(Fallback)

如果想詳細了解這幾種模式可以參考美團技術團隊的總結:服務容錯模式。我們今天要講的是,thanks to the community 多謝社區, Polly已經為我們實現了以上全部的功能。Polly是一個C#實現的彈性瞬時錯誤處理庫(resilience and transient-fault-handling library一直覺得這個英文翻譯不是很好) 。在Polly中,對這些服務容錯模式分為兩類:

錯誤處理fault handling :重試、熔斷、回退

彈性應變resilience:超時、艙壁、緩存

可以說錯誤處理是當錯誤已經發生時,防止由於該錯誤對整個系統造成更壞的影響而設置。而彈性應變,則在是錯誤發生前,針對有可能發生錯誤的地方進行預先處理,從而達到保護整個系統的目地。


定義條件: 定義你要處理的 錯誤異常/返回結果

定義處理方式 : 重試,熔斷,回退

執行

先看一個簡單的例子

// 這個例子展示了當DoSomething方法執行的時候如果遇到SomeExceptionType的異常則會進行重試調用。varpolicy = Policy .Handle()// 定義條件.Retry();// 定義處理方式// 執行policy.Execute(() => DoSomething());


我們可以針對兩種情況來定義條件:錯誤異常和返回結果。

// 單個異常類型Policy.Handle()// 限定條件的單個異常Policy.Handle(ex => ex.Number ==1205)// 多個異常類型Policy.Handle() .Or()// 限定條件的多個異常Policy.Handle(ex => ex.Number ==1205) .Or(ex => ex.ParamName =="example")// Inner Exception 異常裡面的異常類型 Policy.HandleInner() .OrInner(ex => ex.CancellationToken != myToken)

以及用返回結果來限定

// 返回結果加限定條件 Policy.HandleResult(r => r.StatusCode == HttpStatusCode.NotFound)// 處理多個返回結果Policy.HandleResult(r => r.StatusCode == HttpStatusCode.InternalServerError) .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)// 處理元類型結果 (用.Equals)Policy.HandleResult(HttpStatusCode.InternalServerError) .OrResult(HttpStatusCode.BadGateway)// 在一個policy裡面同時處理異常和返回結果。HttpStatusCode[] httpStatusCodesWorthRetrying = {HttpStatusCode.RequestTimeout,// 408HttpStatusCode.InternalServerError,// 500HttpStatusCode.BadGateway,// 502HttpStatusCode.ServiceUnavailable,// 503HttpStatusCode.GatewayTimeout// 504};HttpResponseMessage result = Policy .Handle() .OrResult(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode)) .RetryAsync(...) .ExecuteAsync(/* some Func> */)


在這裡使用的處理方式就是我們最開始說的服務容錯模式,我們將介紹以下三種:重試、熔斷、回退。

重試

重試很好理解,當發生某種錯誤或者返回某種結果的時候進行重試。Polly裡面提供了以下幾種重試機制

按次數重試

不斷重試(直到成功)

等待之後按次數重試

等待之後不斷重試(直到成功)

按次數重試

// 重試1次Policy.Handle() .Retry()// 重試3(N)次Policy.Handle() .Retry(3)// 重試多次,加上重試時的action參數Policy.Handle() .Retry(3, (exception, retryCount) => {// 干點什麼,比如記個日誌之類的});

不斷重試

// 不斷重試,直到成功Policy.Handle() .RetryForever()// 不斷重試,帶action參數在每次重試的時候執行Policy.Handle() .RetryForever(exception => {// do something});

等待之後重試

// 重試3次,分別等待1、2、3秒。Policy.Handle() .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });

當然也可以在每次重試的時候添加一些處理,這裡我們可以從上下文中獲取一些數據,這些數據在policy啟動執行的時候可以傳進來。

Policy .Handle() .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) },(exception, timeSpan, context)=>{//dosomething });

把WiatAndRetry抱成WaitAndRetryForever()則可以實現重試直到成功。

熔斷

熔斷也可以被作為當遇到某種錯誤場景下的一個操作。以下代碼展示了當發生2次SomeExceptionType的異常的時候則會熔斷1分鐘,該操作後續如果繼續嘗試執行則會直接返回錯誤 。

Policy.Handle().CircuitBreaker(2,TimeSpan.FromMinutes(1));

可以在熔斷和恢復的時候定義委託來做一些額外的處理。onBreak會在被熔斷時執行,而onReset則會在恢復時執行。

Action onBreak =(exception, timespan)=>{ ... };Action onReset =()=>{ ... };CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

熔斷器狀態

我們的CircuitBreakPolicy的State定義了當前熔斷器的狀態,我們也可能調用它的Isolate和Reset方法來手動熔斷和恢復 。

CircuitStatestate= breaker.CircuitState;

Closed 關閉狀態,允許執行

Open 自動打開,執行會被阻斷

Isolate 手動打開,執行會被阻斷

HalfOpen 從自動打開狀態恢復中,在熔斷時間到了之後從Open狀態切換到Closed

// 手動打開熔斷器,阻止執行breaker.Isolate();// 恢復操作,啟動執行 breaker.Reset();

回退(Fallback)

// 如果執行失敗則返回UserAvatar.BlankPolicy.Handle() .Fallback(UserAvatar.Blank)// 發起另外一個請求去獲取值Policy.Handle() .Fallback(() => UserAvatar.GetRandomAvatar())// where: public UserAvatar GetRandomAvatar() { ... }// 返回一個指定的值,添加額外的處理操作。onFallbackPolicy.Handle() .Fallback(UserAvatar.Blank, onFallback: (exception, context) => {// do something});


為我聲明了一個Policy,並定義了它的異常條件和處理方式,那麼接下來就是執行它。執行是把我們具體要運行的代碼放到Policy裡面。

// 執行一個Actionvarpolicy = Policy .Handle() .Retry();policy.Execute(() => DoSomething());

這就是我們最開始的例子,還記得我們在異常處理的時候有一個context上下文嗎?我們可以在執行的時候帶一些參數進去

// 看我們在retry重試時被調用的一個委託,它可以從context中拿到我們在execute的時候傳進來的參數 。varpolicy = Policy .Handle() .Retry(3, (exception, retryCount, context) => {varmethodThatRaisedException = context["methodName"]; Log(exception, methodThatRaisedException); });policy.Execute( () => DoSomething(),newDictionary() {{"methodName","some method"}});

當然,我們也可以將Handle,Retry, Execute 這三個階段都串起來寫。

Policy .Handle(ex => ex.Number ==1205) .Or(ex => ex.ParamName =="example") .Retry() .Execute(() => DoSomething());


我們在上面講了Polly在錯誤處理方面的使用,接下來我們介紹Polly在彈性應變這塊的三個應用: 超時、艙壁和緩存。

超時

Policy.Timeout(TimeSpan.FromMilliseconds(2500))

支持傳入action回調

Policy .Timeout(30, onTimeout:(context, timespan, task)=>{//dosomething });

超時分為樂觀超時與悲觀超時,樂觀超時依賴於CancellationToken,它假設我們的具體執行的任務都支持CancellationToken。那麼在進行timeout的時候,它會通知執行線程取消並終止執行線程,避免額外的開銷。下面的樂觀超時的具體用法 。

// 聲明 PolicyPolicy timeoutPolicy = Policy.TimeoutAsync(30);HttpResponseMessage httpResponse =awaittimeoutPolicy .ExecuteAsync(asyncct =>awaithttpClient.GetAsync(endpoint, ct), CancellationToken.None// 最後可以把外部的 CacellationToken附加到 timeoutPollcy的 CT上,在這裡我們沒有附加);

悲觀超時與樂觀超時的區別在於,如果執行的代碼不支持取消CancellationToken,它還會繼續執行,這會是一個比較大的開銷。

Policy.Timeout(30,TimeoutStrategy.Pessimistic)

上面的代碼也有悲觀sad…的寫法

Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Pessimistic);varresponse =awaittimeoutPolicy .ExecuteAsync(async() =>awaitFooNotHonoringCancellationAsync(), );// 在這裡我們沒有 任何與CancllationToken相關的處理

艙壁

在開頭的那篇文章中詳細解釋了艙壁這種模式,它用來限制某一個操作的最大並發執行數量 。比如限制為12

Policy.Bulkhead(12)

同時,我們還可以控制一個等待處理的隊列長度

Policy.Bulkhead(12, 2)

以及當請求執行操作被拒絕的時候,執行回調

Policy .Bulkhead(12, context => {// do something});

緩存

Polly的緩存需要依賴於一個外部的Provider。

varmemoryCacheProvider =newPolly.Caching.MemoryCache.MemoryCacheProvider(MemoryCache.Default);

varcachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5));// 設置一個絕對的過期時間

varcachePolicy = Policy.Cache(memoryCacheProvider,newAbsoluteTtl(DateTimeOffset.Now.Date.AddDays(1));// 設置一個滑動的過期時間,即每次使用緩存的時候,過期時間會更新

varcachePolicy = Policy.Cache(memoryCacheProvider,newSlidingTtl(TimeSpan.FromMinutes(5));// 我們用Policy的緩存機制來實現從緩存中讀取一個值,如果該值在緩存中不存在則從提供的函數中取出這個值放到緩存中。// 借且於Polly Cache 這個操作只需要一行代碼即可。

TResult result = cachePolicy.Execute(() => getFoo(),newContext("FooKey"));// "FooKey" is the cache key used in this execution.// Define a cache Policy, and catch any cache provider errors for logging.varcachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5), (context, key, ex) => { logger.Error($"Cache provider, for key , threw exception: .");// (for example)});

組合Policy

最後我們要說的是如何將多個policy組合起來。大致的操作是定義多個policy,然後用Wrap方法即可。

var policyWrap = Policy .Wrap(fallback,cache, retry, breaker,timeout, bulkhead);policyWrap.Execute(...)

在另一個Policy聲明時組合使用其它外部聲明的Policy。

PolicyWrap commonResilience = Policy.Wrap(retry, breaker, timeout);Avatar avatar = Policy .Handle() .Fallback(Avatar.Blank) .Wrap(commonResilience) .Execute(() => {/* get avatar */});


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 dotNET跨平台 的精彩文章:

祝大家狗年家庭事業旺旺旺
使用Mono將C#編譯運行至WebAssembly平台

TAG:dotNET跨平台 |