创建简单的熔断降级框架

要达到的目标是: 参与降级的方法参数要一样,当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. log4net写入日志到sqlserver数据库

    1.添加log4Net配置文件log4net.config 配置文件属性设置为: 配置如下: <?xml version="1.0" encoding="utf-8 ...

  2. 深入 kernel panic 流程【转】

    一.前言 我们在项目开发过程中,很多时候会出现由于某种原因经常会导致手机系统死机重启的情况(重启分Android重启跟kernel重启,而我们这里只讨论kernel重启也就是 kernel panic ...

  3. 第七章 鼠标(CHECKER4)

    /*--------------------------------------------- CHECKER4.C -- Mouse Hit-Test Demo Program No.4 (c) C ...

  4. CentOS7查询系统版本内核信息

    1. 查看版本号 查看CentOS的版本号命令: [root@localhost ~]# cat /etc/centos-releaseCentOS Linux release 7.4.1708 (C ...

  5. 4.4Python数据类型(4)之字符串函数

    返回总目录 目录: 1.字符串的查找计算 2.字符串的转换 3.字符串的填充压缩 4.字符串的分割拼接 5.字符串的判定 (一)字符串的查找计算 (1)len(str)计算字符串的总数 (2)find ...

  6. January 27th, 2018 Week 04th Saturday

    How long is forever? Sometimes, just one second. 永远有多久?有时候只是一秒. Just one second can make your life t ...

  7. navicat连接mysql时出现2003(10060)错误

    问题解决步骤: 参考http://jingyan.baidu.com/article/95c9d20dac9040ec4f75617a.html,发现是防火墙未关闭: 那么我们就有两种方法去解决了 关 ...

  8. ROS 订阅图像节点(1)

    博客 http://blog.csdn.net/github_30605157/article/details/50990493 参考ROS原网站 http://wiki.ros.org/image_ ...

  9. Jmeter之mysql性能测试

    Jmeter官网地址:https://jmeter.apache.org/ 作为开发人员,必要的性能测试还是需要掌握的,虽然配置druid可以比较直观获得sql的执行时间,那些表被访问的比较多等等,但 ...

  10. linux中VI编写C程序。。。

    在linux中编写C程序时不像编写shell那样开头要#!/bin/bash,但是在C程序中要指定头文件(头文件是指输入输出,宏等,而且要首先声明,也是必须要开始就声明的) 写好C代码后要给C文件赋予 ...