微服务二:熔断降级二

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Fanbin168/article/details/83688587

结合上章所微服务:熔断降级一中所讲的知识点,我们现在来写第一版的降级框架

第一版:简单的降级框架

AspectCore框架的基本使用

1>创建一个.net core的控制台应用程序,我取名叫HystrixTest

2>熔断降级框架 Install-Package Polly -Version 6.0.1(可以安装其他的版本)

3> 安装AspectCore框架服务包:Install-Package AspectCore.Core -Version 0.5.0 (可选其他版本)

4>在控制台应用程序中创建一个HystrixCommandAttribute特性类(名字自取),然后让这个类继承AbstractInterceptorAttribute特性类

HystrixCommandAttribute类的定义如下

using AspectCore.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace HystrixTest
{
    public class HystrixCommandAttribute : AbstractInterceptorAttribute
    {
        public HystrixCommandAttribute(string fallBackMethod) //从特性的构造函数中获取降级方法名称
        {
            this.FallBackMethod = fallBackMethod;
        }
        public string FallBackMethod { get; set; } //降级方法名称
        public async override Task Invoke(AspectContext context, AspectDelegate next)
        {
            try
            {
                await next(context);
            }
            catch
            {
                //出现异常:如果出现异常则需要调用降级方法
                //第一步:获取降级的方法名称(根据对象获取类,从类中获取方法)
                //第二步:调用降级方法
                //第三步:把降级方法的返回值返回

                //获取降级方法
                MethodInfo fallbackMethodInfo = context.Implementation.GetType().GetMethod(this.FallBackMethod);
                //调用降级方法
                object returnValue = fallbackMethodInfo.Invoke(context.Implementation, context.Parameters);
                //把降级方法的返回值返回
                context.ReturnValue = returnValue; //把降级方法的放回值作为我们的返回值进行返回
                
            }
        }
    }
}

5>在项目中添加一个Person类

在Person类中定义三的虚方法,注意,一定要是虚方法,例如在Send_YD方法上打上我们上面定义的HystrixCommandAttribute特性标签,这样当我们在调用这个Person类Send_YD方法的时候,就会被HystrixCommandAttribute拦截。(其实它就相当于我们MVC中的过滤器)(应用场景:我们在做微服务的时候,处理服务的熔断降级。例如:我在Person类中定义3个方法A,B,C,A方法中调用移动发送短信,B方法中调用联通发送短信,C方法中调用电信发送短信。正常情况下我们调用的是A方法发送短信,但是如果调用A方法失败的时候我们可以降级到B方法,如果B方法还是失败,我们可以降级到C方法)

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace HystrixTest
{
    public class Person
    {
        //要用AspectCore这个AOP框架,这个方法必须是虚方法,然后在方法上打上HystrixCommand特性标签
        [HystrixCommand(nameof(Send_LT))] //这个特性的作用是:如果Send_YD方法调用失败,则降级到Send_LT方法
        public async virtual Task<string> Send_YD(string msg)
        {
            Console.WriteLine("移动短信服务开始调用");

            using (HttpClient http = new HttpClient())
            {
                //这里的实际代码是使用httpClient去调用移动接口服务
                var a = 1; var b = 0; var c = a / b;
                Console.WriteLine(msg);
            }
            Console.WriteLine("移动短信服务调用完毕");
            return "OK";
        }

        [HystrixCommand(nameof(Send_DX))] //这个特性的作用是:如果Send_LT方法调用失败,则降级到Send_DX
        public async virtual Task<string> Send_LT(string msg)
        {
            Console.WriteLine("联通短信服务开始调用");
            using (HttpClient http = new HttpClient())
            {
                //这里的实际代码是使用httpClient去调用联通接口服务
                Console.WriteLine(msg);

            }
            Console.WriteLine("联通短信服务调用完毕");
            return "OK";
        }
        public async virtual Task<string> Send_DX(string msg)
        {
            Console.WriteLine("电信短信服务开始调用");
            using (HttpClient http = new HttpClient())
            {
                //这里的实际代码是使用httpClient去调用电信接口服务    
                Console.WriteLine(msg);
            }
            Console.WriteLine("电信短信服务调用完毕");
            return "OK";
        }
    }
}

6>控制台中调用

using AspectCore.DynamicProxy;
using System;

namespace AopTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //通过AspectCore创建代理对象
            ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
            using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
            {
                //必须要通过这种方式拿到Person类对象(其实这里是拿到Person类的子类对象,这个子类名称虽然也叫Person,但是它与我们声明的Person类不在同一个命名空间下,这子类重写了父类Persond的Say方法,这就是为什么Pserson类中的Say方法必须是虚方法的原因)
                Person p = proxyGenerator.CreateClassProxy<Person>();
                p.Send_YD("你好,中国");
            }
            Console.WriteLine("程序执行完毕");
            Console.ReadKey();         
        }
    }
}

第二版熔断降级框架:

这是杨中科同志持续维护的开源项目,如果有需要可以直接使用它的开源框架,以下我是把它的源代码放到我的项目中
github最新地址 https://github.com/yangzhongke/RuPeng.HystrixCore
Nuget地址:https://www.nuget.org/packages/RuPeng.HystrixCore

1>创建一个.net core的HystrixTest控制台应用程序

2>熔断降级框架 Install-Package Polly -Version 6.0.1(可以安装其他的版本)

3> 安装AspectCore框架服务包:Install-Package AspectCore.Core -Version 0.5.0 (可选其他版本)

4>安装:Install-Package Microsoft.Extensions.Caching.Memory (.net自带内存缓存)

5>在控制台应用程序中创建一个HystrixCommandAttribute特性类(名字自取),然后让这个类继承AbstractInterceptorAttribute特性类

HystrixCommandAttribute类的定义如下

using AspectCore.DynamicProxy;
using Polly;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace HystrixTest
{
    [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;
        /// <summary>
        /// 降级方法名称
        /// </summary>
        public string FallBackMethod { get; set; }


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

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

        public HystrixCommandAttribute(string fallBackMethod)
        {
            this.FallBackMethod = fallBackMethod;
        }



        public async override Task Invoke(AspectContext context, AspectDelegate next)
        {
            //一个HystrixCommand中保持一个policy对象即可
            //其实主要是CircuitBreaker要求对于同一段代码要共享一个policy对象
            //根据反射原理,同一个方法的MethodInfo是同一个对象,但是对象上取出来的HystrixCommandAttribute
            //每次获取的都是不同的对象,因此以MethodInfo为Key保存到policies中,确保一个方法对应一个policy实例
            policies.TryGetValue(context.ServiceMethod, out Policy policy);
            lock (this)//因为Invoke可能是并发调用,因此要确保policies赋值的线程安全
            {
                if (policy == null)
                {
                    policy = Policy.NoOpAsync();//创建一个空的Policy

                    if (EnableCircuitBreaker) //如果启动用了熔断策略
                    {
                       
                        //定义一个熔断策略
                        var policyCircuitBreaker = Policy.Handle<Exception>().CircuitBreakerAsync(this.ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(this.MillisecondsOfBreak));

                        policy = policy.WrapAsync(policyCircuitBreaker);
                    }
                    if (this.TimeOutMilliseconds > 0) //如果启用了超时策略
                    {
                        var policyTimeout = Policy.TimeoutAsync(this.TimeOutMilliseconds, Polly.Timeout.TimeoutStrategy.Pessimistic);
                        policy = policy.WrapAsync(policyTimeout);
                    }
                    if (MaxRetryTimes > 0)//如果启用了重试策略
                    {
                        var policyRetry = Policy.Handle<Exception>().RetryAsync(this.MaxRetryTimes, (e, i) => TimeSpan.FromMilliseconds(this.RetryIntervalMilliseconds));
                        policy = policy.WrapAsync(policyRetry);
                    }

                    Policy policyFallBack = Policy
                         .Handle<Exception>()
                         .FallbackAsync(async (ctx, t) =>
                         {
                             //第一步:从AspectContext中拿到当前请求真实的context
                             //这里拿到的aspectContext就是最下面的那段await policy.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);


                             //不能如下这样,因为这是闭包相关(我们在前面有一句代码是这样的if (policy == null) 如果policy为空的时候我们给policy赋值了,但是当第二次再来调用该方法的时候,policy已经有值了,如果这样写第二次调用Invoke的时候context指向的还是第一次的对象,所以要通过Polly的上下文来传递AspectContext )                        
                             //context.ReturnValue = fallBackResult;

                             //第四步:把降级方法的返回值返回
                             aspectContext.ReturnValue = fallBackResult;
                         }, async (ex, t) =>
                         {
                             //这里是捕获异常,ex是异常信息
                         });
                    policy = policyFallBack.WrapAsync(policy);
                    //放入
                    policies.TryAdd(context.ServiceMethod, policy);
                }
            }

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

            //使用.net自带的缓存: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);
            }
        }
    }
}
    

6>创建一个Person类

Person类定义如下

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace HystrixTest
{
    public class Person
    {
        //要用AspectCore这个AOP框架,这个方法必须是虚方法,然后在方法上打上HystrixCommand特性标签
        [HystrixCommand(nameof(Send_LT),MaxRetryTimes =8,EnableCircuitBreaker =true)] //这个特性的作用是:如果Send_YD方法调用失败,则降级到Send_LT方法,MaxRetryTimes =8表示出现异常最多重试8次,EnableCircuitBreaker =true表示启用熔断策略
        public async virtual Task<string> Send_YD(string msg)
        {
            Console.WriteLine("移动短信服务开始调用");

            using (HttpClient http = new HttpClient())
            {
                //这里的实际代码是使用httpClient去调用移动接口服务
                var a = 1; var b = 0; var c = a / b;
                Console.WriteLine(c);
            }
            Console.WriteLine("移动短信服务调用完毕");
            return "OK";
        }

        [HystrixCommand(nameof(Send_DX))] //这个特性的作用是:如果Send_LT方法调用失败,则降级到Send_DX
        public async virtual Task<string> Send_LT(string msg)
        {
            Console.WriteLine("联通短信服务开始调用");
            using (HttpClient http = new HttpClient())
            {
                //这里的实际代码是使用httpClient去调用联通接口服务
                Console.WriteLine(msg);

            }
            Console.WriteLine("联通短信服务调用完毕");
            return "OK";
        }
        public async virtual Task<string> Send_DX(string msg)
        {
            Console.WriteLine("电信短信服务开始调用");
            using (HttpClient http = new HttpClient())
            {
                //这里的实际代码是使用httpClient去调用电信接口服务    
                Console.WriteLine(msg);
            }
            Console.WriteLine("电信短信服务调用完毕");
            return "OK";
        }
    }
}

7>控制台Main中调用

using AspectCore.DynamicProxy;
using System;

namespace HystrixTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //通过AspectCore创建代理对象
            ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
            using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
            {
                //必须要通过这种方式拿到Person类对象(其实这里是拿到Person类的子类对象,这个子类名称虽然也叫Person,但是它与我们声明的Person类不在同一个命名空间下,这子类重写了父类Persond的Say方法,这就是为什么Pserson类中的Say方法必须是虚方法的原因)
                Person p = proxyGenerator.CreateClassProxy<Person>();
                p.Send_YD("你好,中国");
            }
            Console.WriteLine("程序执行完毕");
            Console.ReadKey();
        }
    }
}

AspectCore结合asp.net core依赖注入

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

1>其他的都跟上面一样,这里我们创建的项目一个.net core的Web项目

2>在Nuget中安装  Install-Package AspectCore.Extensions.DependencyInjection

3>修改Startup类的ConfigureServices方法,将ConfigureServices方法的放回类型修改成IServiceProvider类型,并添加以下代码

services.AddSingleton<Person>();//单例模式创建Person对象
//return services.BuildAspectCoreServiceProvider();//过期方法:由下面方法替代
return services.BuildDynamicProxyServiceProvider();//表示由AspectCore来接管依赖注入的功能

public IServiceProvider ConfigureServices(IServiceCollection services)//把这个方法的返回值由原来的void改成IServiceProvider
{
    services.AddMvc();
    //services.AddSingleton<Person>();//表示把Person注入。BuildAspectCoreServiceProvider是让aspectcore接管注入。在Controller中就可以通过构造函数进行依赖注入了 (一般单一的类才这样单独用)

    RegisterServices(this.GetType().Assembly, services);// 把当前程序集传递过去,只要程序集中的public类有方法标记HystrixCommandAttribute特性,就将该类进行注册

    //return services.BuildAspectCoreServiceProvider();//过期方法:由下面方法替代
    return services.BuildDynamicProxyServiceProvider();//表示由AspectCore来接管依赖注入的功能
}

//自定义一个注册类
private static void RegisterServices(Assembly asm, IServiceCollection services)
{
    //遍历程序集中的所有public类型
    foreach (Type type in asm.GetExportedTypes()) //asm.GetExportedTypes()表示获取此程序集中定义的公共类型,这些公共类型在程序集外可见
    {
        //判断类中是否有标注了HystrixCommandAttribute的方法
        bool hasHystrixCommandAttr = type.GetMethods()
        .Any(m => m.GetCustomAttribute(typeof(HystrixCommandAttribute)) != null);
        if (hasHystrixCommandAttr)
        {
            services.AddSingleton(type);
        }
    }
}

4>调用:新建一个Webapi控制器类,在控制器中进行依赖注入Person对象

namespace HystrixTest2
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {

        private Person p;

        public ValuesController(Person per)//构造函数注入Person对象
        {
            this.p = per;
        }
        // GET: api/<controller>
        [HttpGet]
        public async Task<IEnumerable<string>> Get()
        {
            //通过AspectCore创建代理对象
            //ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
            //using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
            //{
            //    //必须要通过这种方式拿到Person类对象(其实这里是拿到Person类的子类对象,这个子类名称虽然也叫Person,但是它与我们声明的Person类不在同一个命名空间下,这子类重写了父类Persond的Say方法,这就是为什么Pserson类中的Say方法必须是虚方法的原因)
            //    Person p = proxyGenerator.CreateClassProxy<Person>();
            //   var a= await p.Send_YD("你好,中国");
            //}
            var a = await p.Send_YD("你好");
            return new string[] { "value1", "value2" };
        }
	}
}

猜你喜欢

转载自blog.csdn.net/Fanbin168/article/details/83688587
今日推荐