1. 程式人生 > >熔斷降級(Polly)

熔斷降級(Polly)

write sleep sel 版本 oms 6.0 服務提供者 指令 復制代碼

熔斷降級(Polly)

https://www.cnblogs.com/qhbm/p/9224307.html

一、 什麽是熔斷降級
熔斷就是“保險絲”。當出現某些狀況時,切斷服務,從而防止應用程序不斷地嘗試執行可能會失敗的操作給系統造成“雪崩”,或者大量的超時等待導致系統卡死。

降級的目的是當某個服務提供者發生故障的時候,向調用方返回一個錯誤響應或者替代響應。舉例子:調用聯通接口服務器發送短信失敗之後,改用移動短信服務器發送,如果移動短信服務器也失敗,則改用電信短信服務器,如果還失敗,則返回“失敗”響應;在從推薦商品服務器加載數據的時候,如果失敗,則改用從緩存中加載,如果緩存中也加載失敗,則返回一些本地替代數據。

二、 Polly 簡介
.Net Core 中有一個被.Net 基金會認可的庫 Polly,可以用來簡化熔斷降級的處理。主要功能:重試(Retry);斷路器(Circuit-breaker);超時檢測(Timeout);緩存(Cache);降級(FallBack);

官網:https://github.com/App-vNext/Polly

介紹文章:https://www.cnblogs.com/CreateMyself/p/7589397.html

Nuget安裝指令:Install-Package Polly -Version 6.0.1

Polly 的策略由“故障”和“動作”兩部分組成,“故障”包括異常、超時、返回值錯誤等情況,“動作”包括 降級(FallBack)、重試(Retry)、熔斷(Circuit-breaker)等。

策略用來執行可能會有有故障的業務代碼,當業務代碼出現“故障”中情況的時候就執行“動作”。

由於實際業務代碼中故障情況很難重現出來,所以 Polly 這一些都是用一些無意義的代碼模擬出來。

Polly 也支持請求緩存“數據不變化則不重復自行代碼”,但是和新版本兼容不好,而且功能局限性很大,因此這裏不講。

由於調試器存在,看不清楚 Polly 的執行過程,因此本節都用【開始執行(不調試)】

三、Polly簡單使用
使用Policy的靜態方法創建ISyncPolicy實現類對象,創建方法既有同步方法也有異步方法,根據自己的需要選擇。下面先演示同步的,異步的用法類似。

舉例:當發生ArgumentException異常的時候,執行Fallback代碼。

復制代碼
Policy policy = Policy
.Handle() //故障
.Fallback(() =>//動作
{
  Console.WriteLine("執行出錯");
});
policy.Execute(() => {//在策略中執行業務代碼
//這裏是可能會產生問題的業務系統代碼
Console.WriteLine("開始任務");  throw new ArgumentException("Hello world!");
  Console.WriteLine("完成任務");
});
Console.ReadKey();
復制代碼
如果沒有被Handle處理的異常,則會導致未處理異常被拋出。

還可以用Fallback的其他重載獲取異常信息:

復制代碼
Policy policy = Policy
.Handle() //故障
.Fallback(() =>//動作
{
  Console.WriteLine("執行出錯");
},ex=> {
  Console.WriteLine(ex);
});
policy.Execute(() => {
  //在策略中執行業務代碼
  //這裏是可能會產生問題的業務系統代碼
  Console.WriteLine("開始任務1");
  throw new ArgumentException("Hello1 world!");
  Console.WriteLine("完成任務");
});
復制代碼
如果Execute中的代碼是帶返回值的,那麽只要使用帶泛型的Policy類即可:

復制代碼
Policy policy = Policy
.Handle() //故障
.Fallback(() =>//動作
{
  Console.WriteLine("執行出錯");
  return "降級的值";
});
string value = policy.Execute(() => {
  Console.WriteLine("開始任務");
  throw new Exception("Hello world!");
  Console.WriteLine("完成任務");
  return "正常的值";
});
Console.WriteLine("返回值:"+value);
復制代碼
FallBack的重載方法也非常多,有的異常可以直接提供降級後的值。

()異常中還可以通過lambda表達式對異常判斷“滿足**條件的異常我才處理”,簡單看看試試重載即可。還可以多個Or處理各種不同的異常。

(*)還可以用HandleResult等判斷返回值進行故障判斷等,我感覺沒太大必要。

四、重試處理
復制代碼
Policy policy = Policy
.Handle()
.RetryForever();
policy.Execute(() => {
Console.WriteLine("開始任務");
if (DateTime.Now.Second % 10 != 0)
{
throw new Exception("出錯");
}
Console.WriteLine("完成任務");
});
復制代碼
RetryForever()是一直重試直到成功

Retry()是重試最多一次;

Retry(n) 是重試最多n次;

WaitAndRetry()可以實現“如果出錯等待100ms再試還不行再等150ms秒。。。。”,重載方法很多,不再一一介紹。還有WaitAndRetryForever。

五、 短路保護 Circuit Breaker
  出現N次連續錯誤,則把“熔斷器”(保險絲)熔斷,等待一段時間,等待這段時間內如果再Execute 則直接拋出BrokenCircuitException異常,根本不會再去嘗試調用業務代碼。等待時間過去之後,再執行Execute的時候如果又錯了(一次就夠了),那麽繼續熔斷一段時間,否則就恢復正常。

這樣就避免一個服務已經不可用了,還是使勁的請求給系統造成更大壓力。

復制代碼
Policy policy = Policy
.Handle()
.CircuitBreaker(6,TimeSpan.FromSeconds(5));//連續出錯6次之後熔斷5秒(不會再去嘗試執行業務代碼)。
while(true)
{
Console.WriteLine("開始Execute");
try
{
policy.Execute(() => {
Console.WriteLine("開始任務");
throw new Exception("出錯");
Console.WriteLine("完成任務");
});
}
catch(Exception ex)
{
Console.WriteLine("execute出錯"+ex);
}
Thread.Sleep(500);
}
復制代碼
其計數的範圍是policy對象,所以如果想整個服務器全局對於一段代碼做短路保護,則需要共用一個policy對象。

六、策略封裝,包裹Warp
可以把多個ISyncPolicy合並到一起執行:

policy3= policy1.Wrap(policy2);
執行policy3就會把policy1、policy2封裝到一起執行。

Policy的靜態方法Wrap可以把更多的policy一起封裝:

policy9=Policy.Wrap(policy1, policy2, policy3, policy4, policy5);
七、超時處理
這些處理不能簡單的鏈式調用,要用到Wrap。例如下面實現“出現異常則重試三次,如果還出錯就FallBack”這樣是不行的

Policy policy = Policy.Handle().Retry(3).Fallback(()=> { Console.WriteLine("執行出錯"); });//這樣不行,系統會直接報錯

註意Wrap是有包裹順序的,內層的故障如果沒有被處理則會拋出到外層。

下面代碼實現了“出現異常則重試三次,如果還出錯就FallBack”

復制代碼
Policy policyRetry = Policy.Handle().Retry(3); //出現異常重試三次
Policy policyFallback = Policy
.Handle()
.Fallback(()=> {
Console.WriteLine("降級");
});
//Wrap:包裹。policyRetry在裏面,policyFallback裹在外面。
//如果裏面出現了故障,則把故障拋出來給外面
Policy policy = policyFallback.Wrap(policyRetry);
policy.Execute(()=> {
Console.WriteLine("開始任務");
if (DateTime.Now.Second % 10 != 0)
{
   throw new Exception("出錯");
}
  Console.WriteLine("完成任務");
});
復制代碼
運行結果:

Timeout是定義超時故障,如果超時會拋出TimeoutRejectedException異常。

Policy policy = Policy.Timeout(3, TimeoutStrategy.Pessimistic);// 創建一個3秒鐘(註意單位)的超時策略。
Timeout生成的Policy要和其他Policy一起Wrap使用。

超時策略一般不能直接用,而是和其他封裝到一起用:

復制代碼
Policy policy = Policy
.Handle() //定義所處理的故障
.Fallback(() =>
{
Console.WriteLine("降級");
});
policy = policy.Wrap(Policy.Timeout(2,TimeoutStrategy.Pessimistic));
policy.Execute(()=> {
Console.WriteLine("開始任務");
Thread.Sleep(5000);
Console.WriteLine("完成任務");
});
復制代碼
執行結果:

上面的代碼就是如果執行超過2秒鐘,則直接Fallback。 這個的用途:請求網絡接口,避免接口長期沒有響應造成系統卡死。

八、Polly 的異步用法
所有方法都用Async方法即可,Handle由於只是定義異常,所以不需要異常方法:

帶返回值的例子:

復制代碼
Policy<byte[]> policy = Policy<byte[]>
.Handle()
.FallbackAsync(async c => {
Console.WriteLine("降級");
return new byte[0];
},async r=> {
Console.WriteLine(r.Exception);
});
policy = policy.WrapAsync(
  Policy.TimeoutAsync(2, TimeoutStrategy.Pessimistic, async(context, timespan, task) =>
  {
  Console.WriteLine("timeout");
  })
);
var bytes = await policy.ExecuteAsync(async () => {
Console.WriteLine("開始任務");
HttpClient httpClient = new HttpClient();
var result = await httpClient.GetByteArrayAsync("http://static.rupeng.com/upload/chatimage/20183/07EB793A4C247A654B31B4D14EC64BCA.png");
Console.WriteLine("完成任務");
return result;
});
Console.WriteLine("bytes長度"+bytes.Length);
復制代碼
執行結果:

沒返回值的例子:

復制代碼
Policy policy = Policy
.Handle()
.FallbackAsync(async c => {
Console.WriteLine("降級");
},async ex=> {//對於沒有返回值的,這個參數直接是異常
Console.WriteLine(ex);
});
policy = policy.WrapAsync(Policy.TimeoutAsync(3, TimeoutStrategy.Pessimistic, async(context, timespan, task) =>
{
Console.WriteLine("timeout");
}));
await policy.ExecuteAsync(async () => {
Console.WriteLine("開始任務");
await Task.Delay(5000);//註意不能用Thread.Sleep(5000);
Console.WriteLine("完成任務");
});
復制代碼
執行結果:

熔斷降級(Polly)