写在前面
Polly 是一个 .NET 弹性和瞬态故障处理库,允许开发人员以 Fluent 和线程安全的方式来实现重试、断路、超时、隔离和回退策略。
Polly 的七种策略介绍
重试(Retry): 当出现故障时自动进行重试
断路(Circuit-breaker):当系统遇到严重问题时,快速回馈失败比让用户/调用者等待要好,限制系统出错的体量,有助于系统恢复。
超时(Timeout):当系统超过一定时间的等待,我们就几乎可以判断不可能会有成功的结果,直接去干别的事情。
隔离(Bulkhead Isolation):当系统的一处出现故障时,可能促发多个失败的调用,很容易耗尽主机的资源(如 CPU)。下游系统出现故障可能导致上游的故障的调用,甚至可能蔓延到导致系统崩溃。所以要将可控的操作限制在一个固定大小的资源池中,以隔离有潜在可能相互影响的操作。
回退(Fallback):有些错误无法避免,就要有备用的方案。这个就像浏览器不支持一些新的 CSS 特性就要额外引用一个 polyfill 一样。一般情况,当无法避免的错误发生时,我们要有一个合理的返回来代替失败。
缓存(Cache):一般我们会把频繁使用且不会怎么变化的资源缓存起来,以提高系统的响应速度。如果不对缓存资源的调用进行封装,那么我们调用的时候就要先判断缓存中有没有这个资源,有的话就从缓存返回,否则就从资源存储的地方(比如数据库)获取后缓存起来,再返回,而且有时还要考虑缓存过期和如何更新缓存的问题。Polly 提供了缓存策略的支持,使得问题变得简单。
策略包(Policy Wrap):一种操作会有多种不同的故障,而不同的故障处理需要不同的策略。这些不同的策略必须包在一起,作为一个策略包,才能应用在同一种操作上。这就是文章开头说的 Polly 的弹性,即各种不同的策略能够灵活地组合起来。
通过NuGet安装Polly类库:
官方项目地址: https://github.com/App-vNext/Polly
代码实现
/// <summary> /// FallBack => 当出现故障,则进入降级动作 /// </summary> public static void Case1() { ISyncPolicy policy = Policy.Handle<ArgumentException>() .Fallback(() => { Console.WriteLine("Error occured"); }); policy.Execute(() => { Console.WriteLine("Job Start"); throw new ArgumentException("Hello Polly!"); Console.WriteLine("Job End"); }); } /// <summary> /// Retry => 重试 /// </summary> public static void Case2() { ISyncPolicy policy = Policy.Handle<Exception>().Retry(3); try { policy.Execute(() => { Console.WriteLine("Job Start"); if (DateTime.Now.Second % 10 != 0) { throw new Exception("Special error occured"); } Console.WriteLine("Job End"); }); } catch (Exception ex) { Console.WriteLine("There's one unhandled exception : " + ex.Message); } } /// <summary> /// CircuitBreaker => 短路保护 /// </summary> public static void Case3() { // Stop for 10s after retry 6 times ISyncPolicy policy = Policy.Handle<Exception>() .CircuitBreaker(6, TimeSpan.FromSeconds(10)); while (true) { try { policy.Execute(() => { Console.WriteLine("Job Start"); throw new Exception("Special error occured"); Console.WriteLine("Job End"); }); } catch (Exception ex) { Console.WriteLine("There's one unhandled exception : " + ex.Message); } Thread.Sleep(500); } } /// <summary> /// Timeout 与 Wrap => Wrap是指策略封装,可以把多个ISyncPolicy合并到一起执行。Timeout则是指超时处理,但是超时策略一般不能直接使用,而是其其他策略封装到一起使用。 /// </summary> public static void Case4() { try { ISyncPolicy policyException = Policy.Handle<TimeoutRejectedException>() .Fallback(() => { Console.WriteLine("Fallback"); }); ISyncPolicy policyTimeout = Policy.Timeout(3, Polly.Timeout.TimeoutStrategy.Pessimistic); ISyncPolicy mainPolicy = Policy.Wrap(policyTimeout, policyException); mainPolicy.Execute(() => { Console.WriteLine("Job Start..."); Thread.Sleep(5000); throw new Exception(); Console.WriteLine("Job End..."); }); } catch (Exception ex) { Console.WriteLine($"Unhandled exception : {ex.GetType()} : {ex.Message}"); } } /// <summary> /// 异步方法 /// </summary> public static async void Case5() { var policy = Policy<byte[]>.Handle<Exception>() .FallbackAsync(async c => { Console.WriteLine("Executed Error!"); return new byte[0]; }, async r => { Console.WriteLine(r.Exception); }); policy.WrapAsync(Policy.TimeoutAsync(5, TimeoutStrategy.Pessimistic, async (context, timespan, task) => { Console.WriteLine("Timeout!"); })); var bytes = await policy.ExecuteAsync(async () => { Console.WriteLine("Start Job"); HttpClient httpClient = new HttpClient(); var result = await httpClient.GetByteArrayAsync("https://img-blog.csdnimg.cn/img_convert/50f2b9069f40b88ea8348492d56abb87.png"); Console.WriteLine("Finish Job"); return result; }); Console.WriteLine($"Length of bytes : {bytes.Length}"); }
调用示例
Case1:
Case2:
Case3: