微服务的熔断与降级

1:、 什么是熔断降级

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

2:降级的目的

降级的目的是当某个服务提供者发生故障的时候,向调用方返回一个错误响应或者替代 响应。举例子:调用联通接口服务器发送短信失败之后,改用移动短信服务器发送,如果移 动短信服务器也失败,则改用电信短信服务器,如果还失败,则返回“失败”响应;在从推 荐商品服务器加载数据的时候,如果失败,则改用从缓存中加载,如果缓存中也加载失败, 则返回一些本地替代数据。

一:Polly 简单使用

建立一个NETCore控制台项目:安装NuGet包:Install-Package Polly -Version 6.0.1

在Program.cs中添加一下代码,每一个部分分为一种方法

启动项目的方法最好用cmd命令执行也可实例调试,命令执行:dotnet PollyTest.dll(PollyTest.dll代表该项目bin目录下的程序dll);

using Polly;
using System;

namespace PollyTest
{
    class Program
    {
        static void Main(string[] args)
        {
            #region 异常捕获    第一种方法
            //实例化
            Policy policy = Policy
           .Handle<ArgumentException>() //故障
           .Fallback(() =>//动作
           {
               Console.WriteLine("执行出错");
           });
            policy.Execute(() =>
            {//在策略中执行业务代码

                  //这里是可能会产生问题的业务系统代码
                  Console.WriteLine("开始任务");
                throw new ArgumentException("Hello world!");
                Console.WriteLine("完成任务");
            });
            #endregion



            #region 重载异常    第二种方法
            //如果没有被Handle处理的异常,则会导致未处理异常被抛出。还可以用Fallback的其他重载获取异常信息:
            Policy policy = Policy
            .Handle<ArgumentException>() //故障
            .Fallback(() =>//动作
            {
                Console.WriteLine("执行出错");
            }, ex =>
            {
                Console.WriteLine(ex);
            });
            policy.Execute(() =>
            {
                Console.WriteLine("开始任务");
                throw new ArgumentException("Hello world!");
                Console.WriteLine("完成任务");
            });
            #endregion



            #region 返回值    第三种方法
            //如果Execute中的代码是带返回值的,那么只要使用带泛型的Policy<T> 类即可:
            Policy<string> policy = Policy<string>
            .Handle<Exception>() //故障
            .Fallback(() =>//动作
            {
                Console.WriteLine("执行出错");
                return "降级的值";
            });
            string value = policy.Execute(() =>
            {
                Console.WriteLine("开始任务");
                throw new Exception("Hello world!");
                Console.WriteLine("完成任务");
                return "正常的值";
            });
            #endregion

            Console.ReadKey();

        }
    }
}

重试处理

using Polly;
using System;

namespace PollyTest
{
    class Program
    {
        static void Main(string[] args)
        {
            #region 永久重试
            //RetryForever()是一直重试直到成功
            //Retry()是重试最多一次;
            //Retry(n) 是重试最多n次;
            //WaitAndRetry()可以实现“如果出错等待100ms再试还不行再等150ms秒。
            Policy policy = Policy
               .Handle<Exception>()
               .Retry(3);
            policy.Execute(() =>
            {
                Console.WriteLine("开始任务");
                if (DateTime.Now.Second % 10 != 0)
                {
                    throw new Exception("出错");
                }
                Console.WriteLine("完成任务");
            }); 
            #endregion

            Console.ReadKey();

        }
    }
}

短路保护 Circuit Breaker

出现N次连续错误,则把“熔断器”(保险丝)熔断,等待一段时间,等待这段时间内如果再Execute 则直接抛出BrokenCircuitException异常,根本不会再去尝试调用业务代码。等待时间过去之后,再 执行Execute的时候如果又错了(一次就够了),那么继续熔断一段时间,否则就恢复正常。 这样就避免一个服务已经不可用了,还是使劲的请求给系统造成更大压力。

using Polly;
using System;

namespace PollyTest
{
    class Program
    {
        static void Main(string[] args)
        {
             #region 短路保护
             Policy policy = Policy
            .Handle<Exception>()
            .CircuitBreaker(6, TimeSpan.FromSeconds(5));//连续出错6次之后熔断5秒(不会再去尝试执行业务代码)。 
            while (true)
            {
                Console.WriteLine("开始Execute");
                try
                {
                    policy.Execute(() =>
                    {
                        Console.WriteLine("开始任务");
                        throw new Exception("出错");
                        Console.WriteLine("完成任务");
                    });
                }
                catch (Exception ex)
                {
                    Console.WriteLine("execute出错" + ex);
                }
                Thread.Sleep(500);
            }

            Console.ReadKey();
            #endregion
        }
    }
}

策略封装

可以把多个ISyncPolicy合并到一起执行: policy3= policy1.Wrap(policy2);

执行policy3就会把policy1、policy2封装到一起执行

policy9=Policy.Wrap(policy1, policy2, policy3, policy4, policy5);把更多一起封装。

超时处理

            #region 超时处理
            //下面代码实现了“出现异常则重试三次,如果还出错就FallBack”
            Policy policyRetry = Policy.Handle<Exception>()
            .Retry(3);
            Policy policyFallback = Policy.Handle<Exception>()
            .Fallback(() =>
            {
                Console.WriteLine("降级");
            });
            //Wrap:包裹。policyRetry在里面,policyFallback裹在外面。
            //如果里面出现了故障,则把故障抛出来给外面
            Policy policy = policyFallback.Wrap(policyRetry);
            policy.Execute(() =>
            {
                Console.WriteLine("开始任务");
                if (DateTime.Now.Second % 10 != 0)
                {
                    throw new Exception("出错");
                }
                Console.WriteLine("完成任务");
            });
            #endregion

二:AOP 框架基础

要求懂的知识:AOP、Filter、反射(Attribute)。

如果直接使用 Polly,那么就会造成业务代码中混杂大量的业务无关代码。我们使用 AOP (如果不了解 AOP,请自行参考网上资料)的方式封装一个简单的框架,模仿 Spring cloud 中的 Hystrix。 需要先引入一个支持.Net Core 的 AOP,目前我发现的最好的.Net Core 下的 AOP 框架是 AspectCore(国产,动态织入),其他要不就是不支持.Net Core,要不就是不支持对异步方法进行 拦截。MVC Filter

GitHub:https://github.com/dotnetcore/AspectCore-Framework

命令:Install-Package AspectCore.Core -Version 0.5.0

在该控制台项目中添加Nuget包,并且新建一个CustomInterceptorAttribute类

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

namespace PollyTest
{
    //安装NuGet包AspectCore.Core 如果vs是2.1版本则包要选择1.2.0  vs是3.0则直接安装即可
   public class CustomInterceptorAttribute : AbstractInterceptorAttribute
    {
        //每个被拦截的方法中执行
        public async override Task Invoke(AspectContext context, AspectDelegate next)
        {
            try
            {
                Console.WriteLine("Before service call");
                await next(context);//执行被拦截的方法
            }
            catch (Exception)
            {
                Console.WriteLine("Service threw an exception!");
                throw;
            }
            finally
            {

                Console.WriteLine("After service call");
            }
        }
    }
}

在该控制台项目中新建一个被代理拦截的Person类 

 public class Person
    {
        [CustomInterceptor]
        public virtual void Say(string msg)
        {
            Console.WriteLine("service calling..." + msg);
        }  
    }

 

在该项目下的程序入口Program类中写入代码进行测试

static void Main(string[] args)
{
    //创建代理
    ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
    using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
    {
        Person p = proxyGenerator.CreateClassProxy<Person>();
        p.Say("rupeng.com");
    }
}

三:创建简单的熔断降级框架

熔断降级初级框架实现

1:在该控制台项目中新建一个HystrixCommandAttribute类

[AttributeUsage(AttributeTargets.Method)]
    public class HystrixCommandAttribute : AbstractInterceptorAttribute
    {
        #region 初级框架
        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
                var fallBackMethod =

               context.ServiceMethod.DeclaringType.GetMethod(this.FallBackMethod);
                //反射调用方法 反射的方法Invoke();
                Object fallBackResult = fallBackMethod.Invoke(context.Implementation,
               context.Parameters);
                context.ReturnValue = fallBackResult;
            }
        }
        #endregion
}

2:将该控制台项目中Person类中添加方法(Person类没有就新建,因为这里已经创建过了是直接在上述的基础上进行添加)

public class Person
    {
        [HystrixCommand("TengXunBackAsync")] //TengXunBackAsync表示如果小米短信失败调的下一个方法名称
        public virtual async Task<string> XiaoMiAsync(string name)
        {
            throw new Exception("小米短信出现异常!");//自定义抛出一个异常 测试小米失败是否会跳到腾讯短信
            Console.WriteLine("小米短信" + name);
            return "ok";
        }
        [HystrixCommand("HuaWeiBackAsync")]
        public virtual async Task<string> TengXunBackAsync(string name)
        {
            throw new Exception("腾讯短信出现异常!");//自定义抛出一个异常 测试腾讯失败是否会跳到华为短信
            Console.WriteLine("腾讯短信" + name);
            return "ok";
        }

        public async Task<string> HuaWeiBackAsync(string name)
        {
            //如果该方法也想在出现异常时调用另一个方法 那本方法就要加一个virtual的修饰符
            Console.WriteLine("华为短信" + name);
            return "ok";
        }

    }

3:在程序入口Program.cs中进行测试

//创建代理
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
   Person p = proxyGenerator.CreateClassProxy<Person>();
   p.XiaoMiAsync("rupeng.com");
}

四:细化框架

在HystrixCommandAttribute类中添加细化初级框架的代码

添加NuGet包Microsoft.Extensions.Caching.Memory

HystrixCommandAttribute类如下:

[AttributeUsage(AttributeTargets.Method)]
    public class HystrixCommandAttribute : AbstractInterceptorAttribute
    {

        #region 细化初级框架
        /// <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 IAsyncPolicy policy;
        //NuGet包Microsoft.Extensions.Caching.Memory
        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, int maxRetryTimes)
        {
            this.FallBackMethod = fallBackMethod;
            this.MaxRetryTimes = maxRetryTimes;
        }
        /// <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)
                    {
                        //策略封装最多试几次
                        policy = policy.WrapAsync(Policy.Handle<Exception>().WaitAndRetryAsync(MaxRetryTimes, i =>
                       TimeSpan.FromMilliseconds(RetryIntervalMilliseconds)));
                    }
                    IAsyncPolicy 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);
            }
        } 
        #endregion
}

将该控制台项目中Person类中修改方法

public class Person
    {
        [HystrixCommand("TengXunBackAsync",3)]
        //("TengXunBackAsync",3)第一个参数表示如果小米短信失败调的下一个方法,第二个表示如果失败重试的次数
        //更多参数属性的使用效果可参照该项目HystrixCommandAttribute类中属性
        public virtual async Task<string> XiaoMiAsync(string name)
        {
            Console.WriteLine("小米短信");
            throw new Exception("小米短信出现异常!");//如果在规定的请求数类全部失败则会进入下一个方法  本次测试时测试三次重试
            Console.WriteLine("小米短信" + name);
            return "ok";
        }
        [HystrixCommand("HuaWeiBackAsync",3)]
        public virtual async Task<string> TengXunBackAsync(string name)
        {
            Console.WriteLine("腾讯短信");
            throw new Exception("腾讯短信出现异常!");
            Console.WriteLine("腾讯短信" + name);
            return "ok";
        }

        public async Task<string> HuaWeiBackAsync(string name)
        {
            //如果该方法也想在出现异常时调用另一个方法 那本方法就要加一个virtual的修饰符
            Console.WriteLine("华为短信" + name);
            return "ok";
        }
    }

 最后Program.cs中进行测试

//创建代理
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
   Person p = proxyGenerator.CreateClassProxy<Person>();
   p.XiaoMiAsync("rupeng.com");
}
   Console.ReadKey();

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值