[聚合文章] Polly

.Net 2018-01-06 28 阅读

Polly 是 .Net 下的一套瞬时故障处理及恢复的函式库,可让开发者以fluent及线程安全的方式来应用诸如 RetryCircuit BreakerTimeoutBulkhead IsolationFallback 等策略。

安装

Nuget:Install-Package Polly

基本用法

var  policy =  Policy
    . Handle < DivideByZeroException >()     //
定义所处理的 故障
    . Retry ();     //
故障 的处理方法

policy. Execute (() =>  DoSomething ());     //
应用策略

从上面的例子中我们可以看出,使用该策略一般包括三个步骤:

  1. 定义所处理的故障
  2. 定义故障的处理方法
  3. 应用策略

上述代码在功能上和下面的代码等价:

for ( int   i  = 0;  i  < 2;  i ++)
{
     try
    {
         DoSomething ();
    }
     catch  ( DivideByZeroException )
    {
         if  ( i  > 1)
             throw ;
    }
}

虽然这个例子比较简单,带来的优越性并不明显,但它以一种比较规范的方式定义了异常的处理策略,一来带来了更好的体验,带来了更好的代码可读性,另外,随着异常策略的复杂,它所带来的对代码的简化就更加明显了。下面就稍微详细一点的深入介绍一下:

定义错误(故障)

常见故障定义方式是指定委托执行过程中出现的特定异常,Polly中支持异常处理方式如下:

//  处理指定异常
Policy . Handle < DivideByZeroException >();

// 
处理有条件的指定异常
Policy . Handle < SqlException >(ex => ex. Number  == 1205);

// 
处理多种异常
Policy . Handle < DivideByZeroException >()
    . Or < ArgumentException >();

// 
处理多种有条件的异常
Policy . Handle < SqlException >(ex => ex. Number  == 1205)

    .Or<ArgumentException>(ex => ex.ParamName == "example");

也支持异常的聚合:

Policy . Handle < ArgumentException >()
    . Or < ArgumentException >();

另外,也支持通过返回值判断是否故障:

//  指定错误的返回值
Policy . HandleResult < int >(ret => ret <= 0);

处理策略:重试

常见的处理策略是重试,Polly库中内置了各种常用的重试策略:

//  重试 1
Policy . Handle < TimeoutException >(). Retry ();
// 
重试多次
Policy . Handle < TimeoutException >(). Retry (3);
// 
无限重试
Policy . Handle < TimeoutException >(). RetryForever ();

也支持retry时增加一些额外的行为:

Policy . Handle < TimeoutException >(). Retry (3, (err, countdown, context) =>
{
     // log retry
});

也支持等待并重试:

//  等待并重试
Policy . Handle < TimeoutException >(). WaitAndRetry (3, _ =>  TimeSpan . FromSeconds (3));

处理策略:回退(Fallback)

Fallback策略是在遇到故障是指定一个默认的返回值,

Policy < int >. Handle < TimeoutException >(). Fallback (3);
Policy < int >. Handle < TimeoutException >(). Fallback (() => 3);

当然,遇到没有返回值的也可以指定故障时的处理方法,

Policy . Handle < TimeoutException >(). Fallback (() => { });

使用Fallback时,异常被捕获,返回默认的返回结果。

PS: 带参数的Fallback处理方式貌似在5.0之后发生了变化,成了本文所示的方式,以前是Fallback<T>

处理策略:断路保护(Circuit Breaker)

Circuit Breaker也是一种比较常见的处理策略,它可以指定一定时间内最大的故障发生次数,当超过了该故障次数时,在该时间段内,不再执行Policy内的委托。下面以一个简单的示例演示下该策略的功能:

static void   testPolicy ()
{
     var  circuitBreaker =  Policy . Handle < TimeoutException >()
            . CircuitBreaker (3,  TimeSpan . FromMinutes (1));
     for  ( int   i  = 0;  i  < 5;  i ++)
    {
         try
        {
            circuitBreaker. Execute ( DoSomething );
        }
    
         catch  (Polly.CircuitBreaker. BrokenCircuitException  e)
        {
             Console . WriteLine (e. Message );
        }
         catch  ( TimeoutException )
        {
             Console . WriteLine ( "timeout" );
        }
    }
}

static   int   index  = 0;
static   int   DoSomething ()
{
     Console . WriteLine ( $ "DoSomething  { index ++} " );
     throw   new   TimeoutException ();
}

执行结果如下:

DoSomething 0

timeout

DoSomething 1

timeout

DoSomething 2

timeout

The circuit is now open and is not allowing calls.

The circuit is now open and is not allowing calls.

可以看到,前面3次都能执行委托DoSomething,但出错次数到达3次后,已经进入断路保护章台,后面两次调用直接返回BrokenCircuitException。直到达到保护时间超时后,对策略的调用才会再次执行DoSomething委托。

这种策略在调用远程服务时非常实用,当一定时间内的调用都出错时,往往可以认为服务提供者已经不可用,后续调用完全可以直接失败,以避免重试的开销。直到一定时间后才需要再次重试。

相对其它处理策略,CircuitBreaker是一个比较复杂的策略,它是有状态的,可以通过CircuitState属性获取:

var state = circuitBreaker. CircuitState ;

它有四种状态:

  • CircuitState.Closed - 常态,可执行actions。
  • CircuitState.Open - 自动控制器已断开电路,不允许执行actions。
  • CircuitState.HalfOpen - 在自动断路时间到时,从断开的状态复原。可执行actions,接续的action/s或控制的完成,会让状态转至Open或Closed。
  • CircuitState.Isolated - 在电路开路的状态时手动hold住,不允许执行actions。

除了超时和策略执行失败的这种自动方式外,也可以手动控制它的状态:

//  手动打开 ( 且保持 ) 一个断路器 例如手动隔离 downstream 的服务
circuitBreaker. Isolate ();

// 
重置一个断路器回 closed 的状态,可再次接受 actions 的执行
circuitBreaker. Reset ();

更多的介绍可以参看官方文档: Circuit Breaker

策略封装(PolicyWrap)

我们可以通过PolicyWrap的方式,封装出一个更加强大的策略:

var fallback =  Policy < int >. Handle < TimeoutException >(). Fallback (100);
var  retry =  Policy < int >. Handle < TimeoutException >(). Retry (2);
var  retryAndFallback = fallback. Wrap (retry);

这个策略就是将Retry和Fallback组合起来,形成一个retry and fallback的策略,也可以写成如下形式:

Policy . Wrap (fallback, retry);

当执行这个新策略时:

retryAndFallback . Execute ( DoSomething );

等价于执行:

fallback. Execute (()=> retry. Execute ( DoSomething ));

策略上下文

在策略的处理过程中,有一个上下文对象,可以在回调函数中使用:

public static   RetryPolicy   Retry ( this   PolicyBuilder  policyBuilder,  int  retryCount,  Action < Exception int Context > onRetry);

它是一个 IDictionary < string object > 类型 的对象,它在Policy的执行过程中都可以使用,如:

Policy . Handle < TimeoutException >(). Retry (3, (err, countDown, context) =>
{
     var  method = context[ "method" ];
     ConsoleLogger . WriteLine (method);
})

这个上下文对象可以在应用策略的时候带入:

policy. Execute ( DoSomething new   Context ( "context" )
{
    [ "method" ] =  "PolicyTest"
});

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。