Polly

.NET foundation 的 Polly 專案是為了解決暫時性錯誤處理(transient-fault-handling)的一個函式庫,裡面提供了處理眾多相關錯誤處理的函式呼叫。

目前的應用程式越來越少是自己獨立作業就把一個 request 或是一項作業給處理完成的,通常都會跟內部或是外部系統合作,而這個部份通常為了解相依關係,會定義好介面常見的就是定義 Api 他的傳入與回傳值,尤其是在這個微服務觀念盛行的時期,這個作法尤其常見。

但是像這種鬆耦合的系統架構,雖然美其名是開發/佈署不會互相影響彼此,但單一服務獨活於系統中又顯的很雞肋,偏偏依賴 Api 的方式其實並不可靠,網路基本上就不是建立在可靠的傳輸之上,所以連線失敗/服務暫時下線可以說是需要假定會隨時發生的事情。

不過很多時候這種連線失敗很可能只是一個突波,下一個 request 可能就成功了,而 Polly 就像是把這些繁瑣設定與工作包起來方便重複使用的函式庫。

其實這個函式庫,跟混亂工程有著密不可分的關係,不過這個部份主題很大,這篇就不提了,只列出一些可以供參考的資料。

Demo source code

GitHub: link

How to install Polly

若你覺得安裝 Package 就是一行 nuget 指令就可以,那其實也沒錯,不過你會發現 Polly 這個套件相依的 packages 預設實在多的驚人。

Install-Package Polly -Version 7.1.1

真的這樣幹的話你會發現,用預設的版本安裝相依套件的話,它會幫你安裝超多 System 相關的 Library, 主要原因就是 NETStandard.Library 預設安裝的版本實在太低,導致這些東西會一起裝進去,若想要避免這種情況,的話就需要先把相依的套件先升級。

  • Install Microsoft.NETCore.Platforms

這是為了 NETStandard 2.x 要安裝的 預設版本號是需要大於 1.1.0 不過這邊可以先升到頂

  • Install NETStandard.Library

這邊就直接裝到 2.x 以上的版本,Polly 目前的版本 (7.1.1) 預設最低版號要求到 1.6.1,但若真的只裝到 1.6.1 的話就需要安裝一堆 System 相關的套件,所以不太推薦。

  • Install Polly

這個時候安裝 Polly 就會發現它不會發瘋似的要求你安裝一堆看起來系統本身就有的東西。乾淨又清爽。

Polly Resilience policies

Polly 提供很多錯誤處理的方法,這些可以解決大多數發生的暫時性錯誤問題,而且這些方法還可以用 PolicyWrap 給包裝成一組錯誤處理方法。以下僅列出部份 Polly 這個 Library 內建好的一些錯誤處理機制和應用情境。

Policy適用情境
Retry概念:錯誤只是暫時的,它會自己修復好。像是網路連線問題,發生突波讓連線暫時很難連上。
Circuit-breaker概念:暫時停止送出 request 給一個已經發生錯誤的系統/服務。若藉由暫緩 request 的進入可以讓服務自行恢復那就暫止 request 進入該服務一段時間。
Timeout概念:request 超過一定的時間就會返回 timeout 錯誤訊息。
Cache概念:通常回應的東西在一定時間內不會改變,或是通常改變的機率不大,且資訊的即時性沒有相當的要求。

Resilience policies - Retry

Retry 這邊基本的用法簡單易懂,就是發生指定的錯誤就讓它馬上再打指定次數

1
2
3
4
5
6
7
8
// 基本的 Retry Policy 就是這樣設定,Retry 的第一個參數表示發生 NullReferenceException 就讓它再打兩次
// 第二個參數表示每次 retry 前要呼叫的委派方法(這是個 Action 不用回傳值)
ISyncPolicy redoPolicy = Policy.Handle<NullReferenceException>()
                               .Retry(2,
                               (exception, count) =>
                               {
                                   Console.WriteLine($"NullReferenceException, exception:{exception.Message}, count:{count}");
                                });

而 retry 還有另一種等待指定時間後再 try 一次

1
2
3
4
5
6
7
8
9
// WairAndRetry 第一個參數跟 Retry 一樣都是表示要再打幾次
// 第二個參數則是個委派,表示要等待多久再打一次 (這是個 Func 需要回傳 Timespan 表示間隔時間)
// WaitAndRetry 的委派的第一個參數是目前已經 retry 的次數
ISyncPolicy redoPolicy = Policy.Handle<ApplicationException>()
                               .WaitAndRetry(2, retryDurationProvider =>
                               {
                                   Console.WriteLine($"ApplicationException, count:{retryDurationProvider}");
                                   return TimeSpan.FromSeconds(Math.Pow(2, retryDurationProvider));
                                });

上述只是設定 Policy ,要讓這些 Policy 發揮作用的話需要讓執行方法在這個 Policy 中呼叫

1
2
3
4
5
// 將你要套用 Policy 的方法利用這個方式執行,這個方法可以接受 Func 跟 Action 兩種委派
redoPolicy.Execute(() =>
{
    // the method you want to execute
});

Resilience policies - Circuit-breaker

斷路器的運作基本上不脫離以下這個狀態機

state machine

有 Open / Closed / Half Open 這三個,而 Api 的呼叫還多了一個 Isolated 這個狀態。不過主要還是針對那三個狀態做不同的處理。

一般的情況下斷路器的狀態若是 Closed 代表系統正常運作中,程式會執行原本要去執行的區段。若是發生指定捕捉的 Exception 的話且有達到設定的上限時就會讓狀態變成 Open。在 Open 狀態下斷路器就會執行 OnBreak(若有設定 OnBreak function 的話)的程式區段,而預設的情況下是會直接把該 Exception 直接拋出。

斷路器的基本用法就是停止呼叫該方法(API或操作)一段時間,在指定時間內都直接返回 Exception。時間過後再次嘗試呼叫該方法。

1
2
3
4
5
// 這段設定是說,若該呼叫觸發指定的 Exception 兩次的話,在 3 秒內的呼叫就直接返回該 Exception,並不會呼叫到該方法。
Policy.Handle<Exception>()
          .CircuitBreaker(
                exceptionsAllowedBeforeBreaking: 2,
                durationOfBreak: TimeSpan.FromSeconds(3))

除了上述的基本用法其實這個方法還有更進階的選項,讓使用上可以控制更多細節

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Policy.Handle<Exception>()
      .AdvancedCircuitBreaker(
          // 這是說呼叫時有大於等於 50% 的機率發生捕捉的 Exception 發生時
          failureThreshold: 0.5,
          // 取樣的時間。這邊就是說 10 秒內發生捕捉 Exception 的機率大於等於 50% 時
          samplingDuration: TimeSpan.FromSeconds(10),
          // 斷路器要打開的另一個條件,在取樣時間內至少要有 8 次或以上的呼叫
          minimumThroughput: 8,
          // 設定要斷開連結多久
          durationOfBreak: TimeSpan.FromSeconds(10),
          onBreak: (exception, timespan) =>
          {
              /*
              這是當斷路器狀態為 CircuitState.Open 時會觸發的 function
              */
          },
          onReset: () =>
          {
              /*
              當呼叫 CircuitBreakerPolicy 的 Reset 方法時會觸發的方法。
              或是當斷路器的狀態從 Half Open 因為成功呼叫而自動恢復到 Closed 時會觸發。
              ps. Reset 方法是可以主動呼叫的
              */
          },
          onHalfOpen: () =>
          {
              /*
              這是當斷路器狀態為 CircuitState.HalfOpen 時會觸發的 function。
              斷路器的狀態會切到 Half Open 會發生在狀態已為 Open 但是已經過指定的時間(這邊就是 durationOfBreak 所指定的時間)時,當下一個 request 再來時,會將狀態切為 Half Open。
              */
          });

因為斷路器需要記憶當前的狀態,所以 CircuitBreakerPolicy 這個實體必須要獲得到同一個,若使用 IoC 容器並讓 Policy 每次取得的都是新的話,就會讓這個斷路器的運作不如預期。詳情可以參考一下這篇文章

Resilience policies - Cache

這個 Policy 其實就是 Cache,恩…這是廢話,也是這個 Policy 的全部。不過這個 Cache 他有分成 in-memory 和 distribute cache (eg. Redis, Distributed MSSQL Server Cache)這兩個不同的套件。

它可以設定 Cache 的 TTL (恩…很基本),不過它還可以設定 Cache 在存放時 / 取得時 / 沒打到時 / 發生各種錯誤時 的對應方法,這點倒是滿不錯的,讓你在處理 Cache 時有更多控制權。

Resilience policies - PolicyWrap

個人覺得 Polly 會好用很大部份歸功於它可以讓眾多 Policy 可以組合在一起使用,這也比較符合一般的使用情境。一個簡單的執行範例像是以下例子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
ISyncPolicy rule1 = Policy.Handle<ApplicationException>()
                          .Retry(1,
                          (exception, count) =>
                          {
                              Console.WriteLine($"ApplicationException, exception:{exception.Message}, count:{count}");
                          });
ISyncPolicy rule2 = Policy.Handle<InvalidOperationException>()
                          .CircuitBreaker(
                              exceptionsAllowedBeforeBreaking: 2,
                              durationOfBreak: TimeSpan.FromSeconds(3));

// Warp 參數接受任何有實做 ISyncPolicy 的物件,所以其實可以將上述的變數用 var 來宣告是可以的。
ISyncPolicy myPolicyWrap =
            Policy.Wrap(rule1,
                        rule2);

myPolicyWrap.Execute(
    () =>
    // do the work
    );

本來一個作業發生的 Exception 就不太可能只有一種,而發生不同的 Exception 需要不同的處理方式也是挺合理的。所以 Warp 方法將這些 Policy 組合在一起就讓這個套件變成了相當好用的神兵利器。

Reference

Polly


Chaos Engineering


Chaos Engineering Tool