创建简单的熔断降级框架

要达到的目标是: 参与降级的方法参数要一样,当HelloAsync执行出错的时候执行HelloFallBackAsync方法。

public class Person
{
  [HystrixCommand("HelloFallBackAsync")]    public virtual async Task<string> HelloAsync(string name)
  {
    Console.WriteLine("hello"+name);    return "ok";
  }
  public async Task<string> HelloFallBackAsync(string name)
  {
    Console.WriteLine("执行失败"+name);     return "fail";
  }}

1、编写 HystrixCommandAttribute

using AspectCore.DynamicProxy;
using System;
using System.Threading.Tasks;
namespace hystrixtest1
{
  //限制这个特性只能标注到方法上
  [AttributeUsage(AttributeTargets.Method)]
  public class HystrixCommandAttribute : AbstractInterceptorAttribute
  {
    public HystrixCommandAttribute(string fallBackMethod)
    {
      this.FallBackMethod = fallBackMethod;
    }
    public string FallBackMethod { get; set; }
    public override async Task Invoke(AspectContext context, AspectDelegate next)
    {
      try
      {
        await next(context);//执行被拦截的方法
      }
      catch (Exception ex)
      {
        //context.ServiceMethod被拦截的方法。context.ServiceMethod.DeclaringType被拦截方法所在的类
        //context.Implementation实际执行的对象p
        //context.Parameters方法参数值
        //如果执行失败,则执行FallBackMethod
        
        //调用降级方法
        //1.调用降级的方法(根据对象获取类,从类获取方法)
        var fallBackMethod = context.ServiceMethod.DeclaringType.GetMethod(this.FallBackMethod);
        //2.调用降级的方法
        Object fallBackResult = fallBackMethod.Invoke(context.Implementation, context.Parameters);
        //3.把降级方法的返回值返回
        context.ReturnValue = fallBackResult;
      }
    }
  }
}

2、编写类

public class Person//需要public类
{
  [HystrixCommand(nameof(HelloFallBackAsync))]          public virtual async Task<string> HelloAsync(string name)//需要是虚方法
  {
    Console.WriteLine("hello"+name);
    String s = null;
    // s.ToString();     return "ok";
  }
  public async Task<string> HelloFallBackAsync(string name)
  {
    Console.WriteLine("执行失败"+name);                   return "fail";
  }
  [HystrixCommand(nameof(AddFall))]           public virtual int Add(int i,int j)
  {
    String s = null;        //s.ToArray();       return i + j;
  }
  public int AddFall(int i, int j)
  {
    return 0;
  }
}

3、创建代理对象

ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder(); using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
  Person p = proxyGenerator.CreateClassProxy<Person>();
  Console.WriteLine(p.HelloAsync("yzk").Result);
  Console.WriteLine(p.Add(1, 2));
}

上面的代码还支持多次降级,方法上标注[HystrixCommand]并且virtual即可:

public class Person//需要public类
{
  [HystrixCommand(nameof(Hello1FallBackAsync))]  public virtual async Task<string> HelloAsync(string name)//需要是虚方法
  {
    Console.WriteLine("hello" + name);               String s = null;                s.ToString();                   return "ok";
  }
  [HystrixCommand(nameof(Hello2FallBackAsync))]        public virtual async Task<string> Hello1FallBackAsync(string name)
  {
    Console.WriteLine("Hello降级1" + name);                      String s = null;        s.ToString();                   return "fail_1";
  }
  public virtual async Task<string> Hello2FallBackAsync(string name)
  {
    Console.WriteLine("Hello降级2" + name);
    return "fail_2";
  }
  [HystrixCommand(nameof(AddFall))]           public virtual int Add(int i, int j)
  {
    String s = null;    s.ToString();                   return i + j;
  }
  public int AddFall(int i, int j)
  {
    return 0;
  }
}

细化框架

上面明白了了原理,然后直接展示写好的更复杂的HystrixCommandAttribute,讲解代码。

这是杨中科老师维护的开源项目

github最新地址 https://github.com/yangzhongke/RuPeng.HystrixCore

Nuget地址:https://www.nuget.org/packages/RuPeng.HystrixCore

重试:MaxRetryTimes表示最多重试几次,如果为0则不重试,RetryIntervalMilliseconds 表示重试间隔的毫秒数;

熔断:EnableCircuitBreaker是否启用熔断,ExceptionsAllowedBeforeBreaking表示熔断前出现允许错误几次,MillisecondsOfBreak表示熔断多长时间(毫秒);

超时:TimeOutMilliseconds执行超过多少毫秒则认为超时(0表示不检测超时)

缓存:CacheTTLMilliseconds 缓存多少毫秒(0 表示不缓存),用“类名+方法名+所有参数值 ToString拼接”做缓存Key(唯一的要求就是参数的类型ToString对于不同对象一定要不一样)。

用到了缓存组件:Install-Package Microsoft.Extensions.Caching.Memory

using System;
using AspectCore.DynamicProxy;
using System.Threading.Tasks;
using Polly;

namespace RuPeng.HystrixCore
{
    [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;

     //由于CircuitBreaker要求同一段代码必须共享同一个Policy对象。
        //而方法上标注的Attribute 对于这个方法来讲就是唯一的对象,一个方法对应一个方法上标注的Attribute对象。
        //一般我们熔断控制是针对一个方法,一个方法无论是通过几个 Person 对象调用,无论是谁调用,只要全局出现ExceptionsAllowedBeforeBreaking次错误,就会熔断,这是框架的实现,你如果认为不合理,自己改去。
        //我们在Attribute上声明一个Policy的成员变量,这样一个方法就对应一个Policy对象。
        private Policy policy;

        private static readonly Microsoft.Extensions.Caching.Memory.IMemoryCache memoryCache = new Microsoft.Extensions.Caching.Memory.MemoryCache(new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions());

        /// <summary>
        ///
        /// </summary>
        /// <param name="fallBackMethod">降级的方法名</param>
        public HystrixCommandAttribute(string fallBackMethod)
        {
            this.FallBackMethod = fallBackMethod;
        }

        public string FallBackMethod { get; set; }

        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            //一个HystrixCommand中保持一个policy对象即可
            //其实主要是CircuitBreaker要求对于同一段代码要共享一个policy对象
            //根据反射原理,同一个方法就对应一个HystrixCommandAttribute,无论几次调用,
            //而不同方法对应不同的HystrixCommandAttribute对象,天然的一个policy对象共享
       //因为同一个方法共享一个policy,因此这个CircuitBreaker是针对所有请求的。
       //Attribute也不会在运行时再去改变属性的值,共享同一个policy对象也没问题
       lock (this)//因为Invoke可能是并发调用,因此要确保policy赋值的线程安全
            {
                if (policy == null)
                {
                    policy = Policy.NoOpAsync();//创建一个空的Policy
            if (EnableCircuitBreaker) //先保证熔断
                    {
                        policy = policy.WrapAsync(Policy.Handle<Exception>().CircuitBreakerAsync(ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(MillisecondsOfBreak)));
                    }
                    if (TimeOutMilliseconds > 0) //控制是否超时
                    {
                        policy = policy.WrapAsync(Policy.TimeoutAsync(() => TimeSpan.FromMilliseconds(TimeOutMilliseconds), Polly.Timeout.TimeoutStrategy.Pessimistic));
                    }
                    if (MaxRetryTimes > 0)  //如果出错等待MaxRetryTimes时间在执行
                    {
                        policy = policy.WrapAsync(Policy.Handle<Exception>().WaitAndRetryAsync(MaxRetryTimes, i => TimeSpan.FromMilliseconds(RetryIntervalMilliseconds)));
                    }
                    Policy policyFallBack = Policy
                    .Handle<Exception>()  //出错了报错   如果出错就尝试调用降级方法
                    .FallbackAsync(async (ctx, t) =>
                    {
                        //这里拿到的就是ExecuteAsync(ctx => next(context), pollyCtx);这里传的 pollyCtx
                        AspectContext aspectContext = (AspectContext)ctx["aspectContext"];
                        var fallBackMethod = context.ServiceMethod.DeclaringType.GetMethod(this.FallBackMethod);
                        Object fallBackResult = fallBackMethod.Invoke(context.Implementation, context.Parameters);
                        //不能如下这样,因为这是闭包相关,如果这样写第二次调用Invoke的时候context指向的
                        //还是第一次的对象,所以要通过Polly的上下文来传递AspectContext
                        //context.ReturnValue = fallBackResult;
                        aspectContext.ReturnValue = fallBackResult;
                    }, async (ex, t) => { });
                    policy = policyFallBack.WrapAsync(policy);
                }
            }

            //把本地调用的AspectContext传递给Polly,主要给FallbackAsync中使用,避免闭包的坑
            Context pollyCtx = new Context();//Context是polly中通过Execute给FallBack、Execute等回调方法传上下文对象使用的
            pollyCtx["aspectContext"] = context;//context是aspectCore的上下文 

            //Install-Package Microsoft.Extensions.Caching.Memory
            if (CacheTTLMilliseconds > 0)
            {
                //用类名+方法名+参数的下划线连接起来作为缓存key
                string cacheKey = "HystrixMethodCacheManager_Key_" + context.ServiceMethod.DeclaringType + "." + context.ServiceMethod + string.Join("_", context.Parameters);
                //尝试去缓存中获取。如果找到了,则直接用缓存中的值做返回值
                if (memoryCache.TryGetValue(cacheKey, out var cacheValue))
                {
                    context.ReturnValue = cacheValue;
                }
                else
                {
                    //如果缓存中没有,则执行实际被拦截的方法
                    await policy.ExecuteAsync(ctx => next(context), pollyCtx);
                    //存入缓存中
                    using (var cacheEntry = memoryCache.CreateEntry(cacheKey))
                    {
                        cacheEntry.Value = context.ReturnValue;//返回值放入缓存
                        cacheEntry.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMilliseconds(CacheTTLMilliseconds);
                    }
                }
            }
            else//如果没有启用缓存,就直接执行业务方法
            {
                await policy.ExecuteAsync(ctx => next(context), pollyCtx);
            }
        }
    }
}

框架不是万能的,不用过度框架,过度框架带来的复杂度陡增,从人人喜欢变成人人恐惧。

结合 asp.net core依赖注入

在asp.net core项目中,可以借助于asp.net core的依赖注入,简化代理类对象的注入,不用再自己调用ProxyGeneratorBuilder 进行代理类对象的注入了。

Install-Package AspectCore.Extensions.DependencyInjection

修改Startup.cs的ConfigureServices方法,把返回值从void改为IServiceProvider

using AspectCore.Extensions.DependencyInjection;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<Person>();
    return services.BuildAspectCoreServiceProvider();
}

其 中 services.AddSingleton<Person>(); 表 示 把Person注 入 。

BuildAspectCoreServiceProvider是让aspectcore接管注入。

在Controller中就可以通过构造函数进行依赖注入了:

public class ValuesController : Controller
{
    private Person p;
    public ValuesController(Person p)
    {
        this.p = p;
    }
}

通过反射扫描所有Service类,只要类中有标记了CustomInterceptorAttribute的方法都算作服务实现类。为了避免一下子扫描所有类,所以 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())
    {
        //判断类中是否有标注了CustomInterceptorAttribute的方法
        bool hasCustomInterceptorAttr = type.GetMethods().Any(m => m.GetCustomAttribute(typeof(CustomInterceptorAttribute)) != null);
        if (hasCustomInterceptorAttr)
        {
            services.AddSingleton(type);
        }
    }
} 

注:此文章是我看杨中科老师的.Net Core微服务第二版和.Net Core微服务第二版课件整理出来的

(7)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 利用Polly+AOP+依赖注入封装的降级框架的更多相关文章

  1. 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发

    <ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...

  2. ASP.NET Core 微服务初探[1]:服务发现之Consul

    ASP.NET Core 微服务初探[1]:服务发现之Consul   在传统单体架构中,由于应用动态性不强,不会频繁的更新和发布,也不会进行自动伸缩,我们通常将所有的服务地址都直接写在项目的配置文件 ...

  3. (8)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot网关(Api GateWay)

    说到现在现有微服务的几点不足: 1) 对于在微服务体系中.和 Consul 通讯的微服务来讲,使用服务名即可访问.但是对于手 机.web 端等外部访问者仍然需要和 N 多服务器交互,需要记忆他们的服务 ...

  4. (5)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 熔断降级(Polly)

    一. 什么是熔断降级 熔断就是“保险丝”.当出现某些状况时,切断服务,从而防止应用程序不断地尝试执行可能会失败的操作给系统造成“雪崩”,或者大量的超时等待导致系统卡死. 降级的目的是当某个服务提供者发 ...

  5. (1)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型

    开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点: 1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块: 2)系统耦合性强,一旦其中一个模块有问题, ...

  6. (10)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot+Identity Server

    用 JWT 机制实现验证的原理如下图:  认证服务器负责颁发 Token(相当于 JWT 值)和校验 Token 的合法性. 一. 相关概念 API 资源(API Resource):微博服务器接口. ...

  7. (6)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- AOP框架

    AOP 框架基础 要求懂的知识:AOP.Filter.反射(Attribute). 如果直接使用 Polly,那么就会造成业务代码中混杂大量的业务无关代码.我们使用 AOP (如果不了解 AOP,请自 ...

  8. ASP.NET Core微服务+Tabler前端框架搭建个人博客1--开始前想说的话

    写在前面 本人为在读研究生,特别喜欢.NET,觉得.NET的编程方式.语法都特别友好,学习.NET Core已经差不多有一年半了,从一开始不知道如何入门到现在终于可以编写一些小的应用程序,想一想还是非 ...

  9. (11)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Thrift高效通讯 (完结)

    一. 什么是 RPC Restful 采用 Http 进行通讯,优点是开放.标准.简单.兼容性升级容易: 缺点是性能略低.在 QPS 高或者对响应时间要求苛刻的服务上,可以用 RPC(Remote P ...

随机推荐

  1. bash shell下最方便的字符串大小写转换方法

    用tr需要新增变量,用declare或typeset需要在变量赋值前或者赋值后单独声明,都有些麻烦 此方法为bash 4.0以后新增,bash 4.0 2009年发布 $ test="abc ...

  2. 用Python实现数据结构之树

    树 树是由根结点和若干颗子树构成的.树是由一个集合以及在该集合上定义的一种关系构成的.集合中的元素称为树的结点,所定义的关系称为父子关系.父子关系在树的结点之间建立了一个层次结构.在这种层次结构中有一 ...

  3. [MapReduce_4] MapTask 并发数的决定机制

    0. 说明 介绍 && Map 个数 & Reduce 个数指定 && Map 切片计算 1. 介绍 一个 job 的 Map 阶段并行度由客户端在提交 job ...

  4. eclipse版本和jdk对应关系

    jdk最新版历史版本下载 http://www.oracle.com/technetwork/java/javase/downloads/index.html http://www.oracle.co ...

  5. 3.7Python数据处理篇之Numpy系列(七)---Numpy的统计函数

    目录 目录 前言 (一)函数一览表 (二)统计函数1 (三)统计函数2 目录 前言 具体我们来学Numpy的统计函数 (一)函数一览表 调用方式:np.* .sum(a) 对数组a求和 .mean(a ...

  6. 简易 Token 验证的实现

    简易 Token 验证的实现 前言 在我们的服务器和客户端的交互中,由于我们的业务中使用 RESTful API 的形式和客户端交互,而 API 又是无状态的,无法帮助我们识别这一次和上一次的请求由谁 ...

  7. UOJ #390. 【UNR #3】百鸽笼

    UOJ #390. [UNR #3]百鸽笼 题目链接 看这道题之前先看一道相似的题目 [PKUWC2018]猎人杀. 考虑类似的容斥: 我们不妨设处理\(1\)的概率. 我们令集合\(T\)中的所有鸽 ...

  8. php 抛出异常信息try catch

    <meta charset="utf-8"> <?php /** * 自定义方法输出异常信息 */ $i=11; try { if ($i==1) { echo ...

  9. df 与 du 已使用空间不一致的原因及解决办法

    通过 df -Th 查看 /var 目录使用了78%, 当登录到/var 目录,du -sh 实际使用112G 分析原因:应该是被删掉的文件 没被真正释放 解决办法: 1.lsof | grep de ...

  10. 转载 互斥体与互锁 <第五篇>

    互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex)).互斥体禁止多个线程同时进入受保护的代码“临界区”.因此,在任意时刻,只有一个线程被允许进入这 ...