写在前面
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
代码实现
cs
/// <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: