Polly 故障处理

Polly是一种.NET弹性和瞬态故障处理库,允许我们以非常顺畅和线程安全的方式来执诸如行重试,断路,超时,故障恢复等策略。

基本用法
一个简单的示例如下:
var policy = Policy
.Handle<Exception>() //定义所处理的故障
.Retry(); //故障的处理方法
policy.Execute(() => Do()); //应用策略

该库实现了七种恢复策略:

一、重试策略(Retry)
重试策略针对的前置条件是短暂的故障延迟且在短暂的延迟之后能够自我纠正。允许我们做的是能够自动配置重试机制。
如下:
// 重试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));

二、断路器(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<TimeoutException>()
.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<TimeoutRejectedException>()
    .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<int>.Handle<TimeoutException>().Fallback(3);
Policy<int>.Handle<TimeoutException>().Fallback(() => 3);
当然,遇到没有返回值的也可以指定故障时的处理方法,
Policy.Handle<TimeoutException>().Fallback(() => { });
使用Fallback时,异常被捕获,返回默认的返回结果。

七、策略包装(PolicyWrap)
策略包装针对的前置条件是不同的故障需要不同的策略,也就意味着弹性灵活使用组合。
如下:
ar 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));

八、策略返回和异常捕获
var result=policy.Execute(Do);//直接返回
异常捕获有两种方式:
①try{} catch{}
②ExecuteAndCapture:
var result = policy.ExecuteAndCapture(DoSomething);
if (result.FaultType == null)
{
Console.WriteLine(result.Result);
}

九、策略上下文
它是一个IDictionary<string, object>类型的对象,它在Policy的执行过程中都可以使用,在执行策略时可以传入;如下:
Policy.Handle<TimeoutException>().Retry(3, (err, count, context) =>
{
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<TimeoutException>().Retry(3));

var policy = registry.Get<ISyncPolicy>("timeoutandretry");
policy.Execute(Do);

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容