Polly熔断降级超时等二次封装

Polly是一个.NET弹性和瞬态故障处理库,允许开发人员以流畅和线程安全的方式表达重试,断路器,超时,隔板隔离和后备等策略。
从版本6.0.1开始,Polly的目标是.NET Standard 1.1和2.0+。
接下来,我将为大家介绍一款极简的熔断降级框架,对Polly进行二次封装,开箱即用,含:超时、熔断、重试,要是还不满足您的需求,您可以根据自身需求,加入舱壁隔离、回退等模式。

1、新建一个类,接着编写我们的熔断降级框架,该框架出自如鹏杨中科。

需要安装一下两个组件:
Install-Package Polly -Version 6.0.1
Install-Package AspectCore.Core -Version 0.7.0

[AttributeUsage(AttributeTargets.Method)] 
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
     /// <summary>
     /// 最多重试几次,如果为0则不重试
     /// </summary>
     public int MaxRetryTimes { get; set; } = 0;

     /// <summary>
     /// 重试间隔的毫秒数
     /// </summary>
     public int RetryIntervalMilliseconds { get; set; } = 100;

     /// <summary>
     /// 是否启用熔断
     /// </summary>
     public bool EnableCircuitBreaker { get; set; } = false;

     /// <summary>
     /// 熔断前出现允许错误几次
     /// </summary>
     public int ExceptionsAllowedBeforeBreaking { get; set; } = 3;

     /// <summary>
     /// 熔断多长时间(毫秒)
     /// </summary>
     public int MillisecondsOfBreak { get; set; } = 1000;

     /// <summary>
     /// 执行超过多少毫秒则认为超时(0表示不检测超时)
     /// </summary>
     public int TimeOutMilliseconds { get; set; } = 0;

     /// <summary>
     /// 缓存多少毫秒(0表示不缓存),用“类名+方法名+所有参数值ToString拼接”做缓存Key
     /// </summary>
     public int CacheTTLMilliseconds { get; set; } = 0;

     private static ConcurrentDictionary<MethodInfo, Policy> policies = new ConcurrentDictionary<MethodInfo, Policy>();

     /// <summary>
     /// 方法名
     /// </summary>
     public string FallBackMethod { get; set; }

     /// <summary>
     /// 记录方法名
     /// </summary>
     /// <param name="fallBackMethod">降级的方法名</param>
     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<Exception>()
                         .CircuitBreakerAsync(ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(MillisecondsOfBreak)));
                 }

                 //是否启用重试
                 if (MaxRetryTimes > 0)
                 {
                     policy = policy.WrapAsync(Policy.Handle<Exception>().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<Exception>()
                     .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);
         }
 }

2、编写我们需要进行熔断降级的业务类

 public class Person
 {
       /// <summary>
       /// 我们的业务方法
       /// MaxRetryTimes:出错重试几次
       /// TimeOutMilliseconds:多少毫秒触发超时
       /// EnableCircuitBreaker:错误等异常触发熔断
       /// </summary>
       /// <param name="name"></param>
       /// <returns></returns>
       [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";
       }
}

3、在控制台或mvc/webapi中调用受Polly管理的业务方法

  我这儿使用控制台进行示范
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder(); 
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build()) 
{  
    Person p = proxyGenerator.CreateClassProxy<Person>();        
    Console.WriteLine(p.HelloAsync("hello world!")); 
} 

4、在mvc或者webapi可以使用 asp.net core 依赖注入 ,就不用写一堆代码了。

安装: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<Person>();
    return services.BuildAspectCoreServiceProvider(); 
}

5、在你的控制器的构造函数中进行依赖注入了

public class ValuesController : Controller 
{
     private Person _person;  
     public ValuesController(Person person) 
     {
         _person = person; 
     }
}

6、每个业务代码都要写一遍services.AddSingleton()肯定是很头疼,我们可以利用反射来帮我们做这件事

通过反射扫描所有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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值