Polly是一种.NET弹性和瞬态故障处理库,允许我们以非常顺畅和线程安全的方式来执诸如行重试,断路,超时,故障恢复等策略。
基本用法
一个简单的示例如下:
var policy = Policy
.Handle
.Retry(); //故障的处理方法
policy.Execute(() => Do()); //应用策略
该库实现了七种恢复策略:
一、重试策略(Retry)
重试策略针对的前置条件是短暂的故障延迟且在短暂的延迟之后能够自我纠正。允许我们做的是能够自动配置重试机制。
如下:
// 重试1次 Policy.Handle
//重试多次 Policy.Handle
//无限重试 Policy.Handle
也支持retry时增加一些额外的行为:
Policy.Handle
{
// log retry
});
同时支持等待并重试:
// 等待并重试 Policy.Handle
二、断路器(Circuit-breaker)
断路器策略针对的前置条件是当系统繁忙时,快速响应失败总比让用户一直等待更好。保护系统故障免受过载,Polly可以帮其恢复。
Circuit-breaker有四种状态,可通过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();
如下:
static void testPolicy()
{
var circuitBreaker = Policy.Handle
.CircuitBreaker(3, TimeSpan.FromMinutes(1));
for (int i = 0; i < 5; i++)
{
try
{
circuitBreaker.Execute(Do);
}
catch (Polly.CircuitBreaker.BrokenCircuitException e)
{
Console.WriteLine(e.Message);
}
catch (TimeoutException)
{
Console.WriteLine("timeout");
}
}
}
static int index = 0;
static int Do()
{
Console.WriteLine($"{index++}");
throw new TimeoutException();
}
执行结果如下:
0
timeout
1
timeout
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委托。
三、超时(Timeout)
超时策略针对的前置条件是超过一定的等待时间,想要得到成功的结果是不可能的,保证调用者不必等待超时。
超时策略常见的重载版本如下:
Policy.Timeout(300);
Policy.Timeout(() => TimeSpan.FromSeconds(3));
Policy.Timeout(TimeSpan.FromSeconds(3), TimeoutStrategy.Optimistic);
支持两种超时策略:
- TimeoutStrategy.Pessimistic: 悲观模式
当委托到达指定时间没有返回时,不继续等待委托完成,并抛超时TimeoutRejectedException异常。 - TimeoutStrategy.Optimistic:乐观模式
依赖于 [co-operative cancellation],只是触发CancellationTokenSource.Cancel函数,需要等待委托自行终止操作。
乐观模式的的策略示例如下:
var policy = Policy.Timeout(300);
var cts = new CancellationTokenSource();
policy.Execute(ct =>
{
for (int i = 0; i < 1000; i++)
{
Thread.Sleep(100);
ct.ThrowIfCancellationRequested();
}
}, cts.Token);
很多时候超时还需要和重试等其它故障处理策略一起使用,如:
Policy.Handle()
.Retry(3)
.Wrap(Policy.Timeout(3, TimeoutStrategy.Pessimistic));
四、隔板隔离(Bulkhead Isolation)
隔板隔离针对的前置条件是当进程出现故障时,多个失败一直在主机中对资源(例如线程/ CPU)一直占用。下游系统故障也可能导致上游失败。这两个风险都将造成严重的后果。都说一粒老鼠子屎搅浑一锅粥,而Polly则将受管制的操作限制在固定的资源池中,免其他资源受其影响。
舱壁隔离是一种并发控制的行为,并发控制是一个比较常见的模式
如下:
//该策略下最多只有10个任务并发执行
Policy.Bulkhead(10);
超过了并发数的任务会抛BulkheadRejectedException,如果要放在队列中等待,Polly也提供了等待队列的支持:
Policy.Bulkhead(10, 100);
这种方式下,有10个并发任务,每个任务维持着一个并发队列,每个队列可以自持最大100个任务。
五、缓存(Cache)
缓存策略针对的前置条件是数据不会很频繁的进行更新,为了避免系统过载,首次加载数据时将响应数据进行缓存,如果缓存中存在则直接从缓存中读取。
过期策略:
Policy.Cache(memoryCacheProvider, new AbsoluteTtl(DateTimeOffset.Now.Date.AddDays(1)));
Policy.Cache(memoryCacheProvider, new SlidingTtl(TimeSpan.FromMinutes(5)));
如下:
var memoryCacheProvider =MemoryCache.Default;
var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5));
//Context.ExecutionKey就是cache的key
var context = new Context("cache_key");
for (int i = 0; i < 3; i++)
{
var cache = cachePolicy.Execute(_ =>
{
Console.WriteLine("get value");
return 3;
}, context);
Console.WriteLine(cache);
}
六、回退(Fallback)
操作仍然会失败,也就是说当发生这样的事情时我们打算做什么。也就是说定义失败返回操作。
如下:
Fallback策略是在遇到故障是指定一个默认的返回值,
Policy
Policy
当然,遇到没有返回值的也可以指定故障时的处理方法,
Policy.Handle
使用Fallback时,异常被捕获,返回默认的返回结果。
七、策略包装(PolicyWrap)
策略包装针对的前置条件是不同的故障需要不同的策略,也就意味着弹性灵活使用组合。
如下:
ar fallback = Policy
var retry = Policy
var retryAndFallback = fallback.Wrap(retry);
这个策略就是将Retry和Fallback组合起来,形成一个retry and fallback的策略,也可以写成如下形式:
Policy.Wrap(fallback, retry);
当执行这个新策略时:
retryAndFallback.Execute(DoSomething);
等价于执行:
fallback.Execute(()=> retry.Execute(DoSomething));
八、策略返回和异常捕获
var result=policy.Execute(Do);//直接返回
异常捕获有两种方式:
①try{} catch{}
②ExecuteAndCapture:
var result = policy.ExecuteAndCapture(DoSomething);
if (result.FaultType == null)
{
Console.WriteLine(result.Result);
}
九、策略上下文
它是一个IDictionary
Policy.Handle
{
var value= context["value"];
ConsoleLogger.WriteLine(value);
})
policy.Execute(Do, new Context("context")
{
["value"] = "Test"
});
十 依赖注入
Polly也自带了一个简单的DI框架:
var registry = new PolicyRegistry();
registry.Add("timeoutandretry", Policy.Handle
var policy = registry.Get
policy.Execute(Do);