Polly是一个.NET弹性和瞬态故障处理库,允许开发人员以流畅和线程安全的方式表达重试,断路器,超时,隔板隔离和后备等策略。
从版本6.0.1开始,Polly的目标是.NET Standard 1.1和2.0+。
接下来,我将为大家介绍一款极简的熔断降级框架,对Polly进行二次封装,开箱即用,含:超时、熔断、重试,要是还不满足您的需求,您可以根据自身需求,加入舱壁隔离、回退等模式。
需要安装一下两个组件:
Install-Package Polly -Version 6.0.1
Install-Package AspectCore.Core -Version 0.7.0
[AttributeUsage(AttributeTargets.Method)]
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
///
/// 最多重试几次,如果为0则不重试
///
public int MaxRetryTimes { get; set; } = 0;
///
/// 重试间隔的毫秒数
///
public int RetryIntervalMilliseconds { get; set; } = 100;
///
/// 是否启用熔断
///
public bool EnableCircuitBreaker { get; set; } = false;
///
/// 熔断前出现允许错误几次
///
public int ExceptionsAllowedBeforeBreaking { get; set; } = 3;
///
/// 熔断多长时间(毫秒)
///
public int MillisecondsOfBreak { get; set; } = 1000;
///
/// 执行超过多少毫秒则认为超时(0表示不检测超时)
///
public int TimeOutMilliseconds { get; set; } = 0;
///
/// 缓存多少毫秒(0表示不缓存),用“类名+方法名+所有参数值ToString拼接”做缓存Key
///
public int CacheTTLMilliseconds { get; set; } = 0;
private static ConcurrentDictionary policies = new ConcurrentDictionary();
///
/// 方法名
///
public string FallBackMethod { get; set; }
///
/// 记录方法名
///
/// 降级的方法名
public HystrixCommandAttribute(string fallBackMethod)
{
this.FallBackMethod = fallBackMethod;
}
public override async Task Invoke(AspectContext context, AspectDelegate next)
{
//一个HystrixCommand中保持一个policy对象即可
//其实主要是CircuitBreaker要求对于同一段代码要共享一个policy对象
//根据反射原理,同一个方法的MethodInfo是同一个对象,但是对象上取出来的HystrixCommandAttribute
//每次获取的都是不同的对象,因此以MethodInfo为Key保存到policies中,确保一个方法对应一个policy实例
policies.TryGetValue(context.ServiceMethod, out Policy policy);
lock (policies)
{
if (policies==null)
{
policy = Policy.NoOpAsync();//创建一个空的Policy
//是否启用熔断
if (EnableCircuitBreaker)
{
policy = policy.WrapAsync(Policy.Handle()
.CircuitBreakerAsync(ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(MillisecondsOfBreak)));
}
//是否启用重试
if (MaxRetryTimes > 0)
{
policy = policy.WrapAsync(Policy.Handle().WaitAndRetryAsync(MaxRetryTimes, i => TimeSpan.FromMilliseconds(RetryIntervalMilliseconds)));
}
//是否启用超时
if (TimeOutMilliseconds>0)
{
policy = policy.WrapAsync(Policy.TimeoutAsync(() => TimeSpan.FromMilliseconds(TimeOutMilliseconds),
Polly.Timeout.TimeoutStrategy.Pessimistic));
}
Policy policyFallBack = Policy
.Handle()
.FallbackAsync(async (ctx, t) =>
{
AspectContext aspectContext = (AspectContext)ctx["aspectContext"];
var fallBackMethod = context.ServiceMethod.DeclaringType.GetMethod(this.FallBackMethod);
Object fallBackResult = fallBackMethod.Invoke(context.Implementation, context.Parameters);
aspectContext.ReturnValue = fallBackResult;
}, async (ex, t) => { });
policy = policyFallBack.WrapAsync(policy);
policies.TryAdd(context.ServiceMethod, policy);
}
}
//把本地调用的AspectContext传递给Polly,主要给FallbackAsync中使用,避免闭包的坑
Context pollyCtx = new Context();
pollyCtx["aspectContext"] = context;
if (CacheTTLMilliseconds>0)
{
//Cach Code...
}
else
{
//没有缓存,则执行实际被拦截的方法
await policy.ExecuteAsync(ctx => next(context), pollyCtx);
}
}
public class Person
{
///
/// 我们的业务方法
/// MaxRetryTimes:出错重试几次
/// TimeOutMilliseconds:多少毫秒触发超时
/// EnableCircuitBreaker:错误等异常触发熔断
///
///
///
[HystrixCommand(nameof(HelloFallBackAsync),MaxRetryTimes =3,EnableCircuitBreaker =true,TimeOutMilliseconds =200)]
public virtual async Task<string> HelloAsync(string name)
{
Console.WriteLine("hello" + name);
//抛出异常,进行熔断
String s = null;
s.ToString();
//触发超时
//await Task.Delay(500);
return "ok";
}
//降级的方法
public async Task<string> HelloFallBackAsync(string name)
{
Console.WriteLine("执行失败" + name);
return "fail";
}
}
我这儿使用控制台进行示范
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
Person p = proxyGenerator.CreateClassProxy();
Console.WriteLine(p.HelloAsync("hello world!"));
}
安装:Install-Package AspectCore.Extensions.DependencyInjection
修改 Startup.cs 的 ConfigureServices 方法,把返回值从 void 改为 IServiceProvider
using AspectCore.Extensions.DependencyInjection;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//将Person注入到BuildAspectCoreServiceProvider,让aspectcore 进行注入。
services.AddSingleton();
return services.BuildAspectCoreServiceProvider();
}
public class ValuesController : Controller
{
private Person _person;
public ValuesController(Person person)
{
_person = person;
}
}
通过反射扫描所有Service 类,只要类中有标记了HystrixCommandAttribute 的方法都算作服务实现类。为了避免一下子扫描所有类,所以 RegisterServices 还是手动指定从哪个程序集中加载。
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
RegisterServices(this.GetType().Assembly, services);
return services.BuildAspectCoreServiceProvider();
}
private static void RegisterServices(Assembly asm, IServiceCollection services)
{
//遍历程序集中的所有public类型
foreach (Type type in asm.GetExportedTypes())
{ //判断类中是否有标注了HystrixCommandAttribute的方法
bool hasHystrixCommandAttr =
type.GetMethods().Any(m => m.GetCustomAttribute(typeof(HystrixCommandAttribute) != null);
if (hasCustomInterceptorAttr)
{
services.AddSingleton(type);
}
}
}
目前该框架已在生产环境跑了有一段时间了,到目前为止,仍未出啥扯犊子的事儿。
欢迎加入.NET CORE/ASP.NET CORE 技术交流群,我们期待你的加入。
群号:702566187