微服务二:熔断降级一

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

一、 什么是熔断降级

当我们在Consul中注册了我们的服务(假设注册了3台服务器)假设有一台服务器挂了,Consul还没来得及注销它的时候,它还是正常在Consul中注册着的。我们向Consul要服务器的时候,Consul可能给我们这台挂掉了的服务器,从而导致请求失败

为了最大限度的避免这种情况发生,就有了我们下面说的熔断降级。

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

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

.Net Core中有一个被.Net基金会认可的库Polly,可以用来简化熔断降级的处理。主要功能:重试(Retry);断路器(Circuit-breaker);超时检测(Timeout);缓存(Cache);降级(FallBack);

官网:https://github.com/App-vNext/Polly

介绍文章:https://www.cnblogs.com/CreateMyself/p/7589397.html

安装服务包:Install-Package Polly -Version 6.0.1(可以安装其他的版本)

Polly的策略由“故障”和“动作”两部分组成,“故障”包括异常、超时、返回值错误等情况,“动作”包括FallBack(降级)、重试(Retry)、熔断(Circuit-breaker)等。 策略用来执行可能会有有故障的业务代码,当业务代码出现“故障”中情况的时候就执行“动作”。 由于实际业务代码中故障情况很难重现出来,所以Polly这一些都是用一些无意义的代码模拟出来。

不带返回值的Policy

using Polly;
using Polly.Fallback;
using System;

namespace PollyFrame
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Policy policy = Policy.Handle<ArgumentException>()//这就是“故障”
                //程序出现故障的时候,会执行Fallback方法 (这个Fallback方法则是“策略”,即出现这个ArgumentException异常的故障了,采用这Fallback方法中的策略)
                 .Fallback(() => 
                 {
                     Console.WriteLine("出错啦,降级"); 
                 }
                 //Fallback方法有很多重载方法,有一个重载方法有两个参数,这是第二个参数
                  , ex =>
                  {
                      Console.WriteLine("在Fallback方法的第三个重载方法中,还可以拿到错误异常的详细信息:" + ex.Message);
                    });
                //这是我们的的代码
                policy.Execute(() =>
                {
                    Console.WriteLine("开始执行代码!");
                    throw new ArgumentException();//代码执行过程中抛出无效参数异常
                    Console.WriteLine("代码执行完毕");
                } );
            }
            catch (Exception ex)
            {
                Console.WriteLine("出现Policy未捕获的错误(因为上面的Policy只监控了ArgumentException错误,出现其他的错误的时候,Policy就没办法捕获了)");
            }           
        }
    }
}

带返回值的的Policy

using Polly;
using Polly.Fallback;
using System;

namespace PollyFrame
{
    class Program
    {
        static void Main(string[] args)
        {

            try
            {
                //关于这个Handle方法有很多重载,比如下面这个方法就规定,只有出现AggregateException错误,并且错误消息是“数据错误”或者有DivideByZeroException异常的时候时候才处理
                //Policy<string> policy = Policy<string>.Handle<AggregateException>(ex=>ex.Message=="数据错误").Or<DivideByZeroException>()
                Policy<string> policy = Policy<string>.Handle<Exception>()//这就是“故障” 程序出现问题的时候,会执行Fallback方法 (这个Fallback方法则是“策略”,即出现这个Exception异常的故障了,采用这Fallback方法中的策略)                                                                     
                 .Fallback(() =>
                 {
                     Console.WriteLine("出错啦,返回降级后的值");
                     //这里是降级代码(在这里向Consul要另外一台服务器,然后用httpClient调用这这台服务器获取数据,这就是降级)
                     return "降级OK";
                 });

                //这是我们的的业务代码
                string value = policy.Execute(() =>
                  {
                      Console.WriteLine("开始执行代码!");
                      int a = 1, b = 0; int c = a / b; //这里人为的制造一个异常;
                      Console.WriteLine("代码执行完毕");
                      return "正常OK";
                  });

                Console.WriteLine("返回值是:+"serverValue); //这里可以拿到返回值
				Console.Read(); 
            }
            catch (Exception ex)
            {
                Console.WriteLine("出现Policy未捕获的错误(因为上面的Policy只监控了ArgumentException错误,出现其他的错误的时候,Policy就没办法捕获了)");
            }         
    }
}

调用服务出错后重试

using Polly;
using Polly.Fallback;
using System;

namespace PollyFrame
{
    class Program
    {
        static void Main(string[] args)
        {

            Policy policy = Policy.Handle<Exception>()
                //.RetryForever();//可选;如果出现异常错误,无限次的重试(一般不用)
                //.WaitAndRetryForever(i => TimeSpan.FromSeconds(i)); //可选,一直重试,i表示当前是重试的第几次,返回值是一个时间戳,表示的意思是,第一次重试等一秒,第二次重试等2秒,第三次重试等3秒...知道成功为止
                //.Retry();//可选;如果出现异常错误,重试一次            
                .Retry(3);//可选,如果出现异常错误,最多重试3次
                //.WaitAndRetry(10, i => a(i)); //可选;即等一等在重试,这里表示最多重试10次,i表示当前是重试的第几次,返回值是一个时间戳,表示的意思是,第一次重试等一秒,第二次重试等2秒,第三次重试等3秒...
            policy.Execute(() =>
            {
                Console.WriteLine("开始任务");
                if (DateTime.Now.Second % 10 != 0) //如果当前的秒数除10取模不等于0就抛出异常
                {
                    throw new Exception("出错");
                }
                Console.WriteLine("完成任务");
            });
        }
        public static TimeSpan a(int i)
        {
            return TimeSpan.FromSeconds(i);
        }
    }
}

短路保护:熔断

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

using Polly;
using Polly.Fallback;
using System;

namespace PollyFrame
{
    class Program
    {
        static void Main(string[] args)
        {

            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) { }             
            }
        }
    }
}

策略的拼接

可以把多个Policy合并到一起执行:
policy3= policy1.Wrap(policy2);
执行policy3就会把policy1、policy2封装到一起执行
policy5=Policy3.Wrap(policy4);把更多一起封装。

using Polly;
using Polly.Fallback;
using System;

namespace PollyFrame
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义个重试三次的策略
            Policy policyRetry = Policy.Handle<Exception>()
                .Retry(3);

            //定义个降级策略
            Policy policyFallback = Policy.Handle<Exception>()
                .Fallback(() =>
                {
                    Console.WriteLine("执行降级代码,进行降级");
                });

            //注意Wrap是有包裹顺序的,内层的故障如果没有被处理则会抛出到外层。(即:如果里层出现了未处理异常,则把异常抛出来给外层,这样就实现了下面代码中的,如果执行policyRetry策略,重试3次还有异常的话,就执行外面的policyFallback降级策略)
            //policyRetry调用Wrap方法包裹住了policyFallback,所以policyRetry是在外层,policyFallback是在里层
            //所以里层的策略代码先执行,
            Policy policy = policyFallback.Wrap(policyRetry);//重试3次还有异常的话,就执行外面的policyFallback降级策略
        }
    }
}

超时故障

在Policy中的Handel是定义异常故障的,而在Polciy中还有一种超时故障

版本1

using Polly;
using Polly.Fallback;
using Polly.Timeout;
using System;
using System.Threading;

namespace PollyFrame
{
    class Program
    {
        static void Main(string[] args)
        {
            //Timeout是定义故障,他和Handel一样,只是Timeout是定义超时故障,Handel是定义异常故障
            //Timeout生成的Policy要配合其他的Policy一起Wrap使用
            //即:超时策略一般不能直接使用,而是和其他的Policy策略用Wrap拼接起来使用
            //如果一段代码出现超时,它会抛出TimeoutRejectedException异常
            Policy policyTimeout = Policy.Timeout(3,Polly.Timeout.TimeoutStrategy.Pessimistic);//创建一个3秒钟的超时策略(即:如果执行一段代码,3秒钟还没执行完,值认定为超时)

            Policy policyFallBack = Policy.Handle<TimeoutRejectedException>()
                .Fallback(() =>
                {
                    Console.WriteLine("执行降级代码进行降级");
                });
            
            Policy policy = policyFallBack.Wrap(policyTimeout);
            policy.Execute(() =>
            {
                Console.WriteLine("开始执行代码");
                Thread.Sleep(5000);
                Console.WriteLine("完成任务");
            });
            Console.ReadKey();
        }
    }
}

版本2 (更多个policy策略拼接使用)

using Polly;
using Polly.Fallback;
using Polly.Timeout;
using System;
using System.Threading;

namespace PollyFrame
{
    class Program
    {
        static void Main(string[] args)
        {
            //Timeout是定义故障,他和Handel一样,只是Timeout是定义超时故障,Handel是定义异常故障
            //Timeout生成的Policy要配合其他的Policy一起Wrap使用
            //即:超时策略一般不能直接使用,而是和其他的Policy策略用Wrap拼接起来使用
            //如果一段代码出现超时,它会抛出TimeoutRejectedException异常
            Policy policyTimeout = Policy.Timeout(3,Polly.Timeout.TimeoutStrategy.Pessimistic);//创建一个3秒钟的超时策略(即:如果执行一段代码,3秒钟还没执行完,值认定为超时)

            Policy policyFallBack = Policy.Handle<TimeoutRejectedException>()
            .Fallback(() =>
                {
                    Console.WriteLine("执行降级代码进行降级");
                });

            //定义一个重试策略
            Policy policyRetry = Policy.Handle<TimeoutRejectedException>().Retry(3);

            Policy policy = policyRetry.Wrap(policyTimeout); //如果超时,则执行重试策略(重试3次)
            Policy policy2 = policyFallBack.Wrap(policy); //如果超时,并重试3次后,还有超时异常则执行降级策略

            policy2.Execute(() =>
            {
                Console.WriteLine("开始执行代码");
                Thread.Sleep(5000);
                Console.WriteLine("完成任务");
            });

            Console.ReadKey();

        }
    }
}

Polly的异步用法

using Polly;
using Polly.Fallback;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace PollyFrame
{
    class Program
    {
        static void Main(string[] args)
        {
            var a = Test().Result;

            Console.ReadKey();
        }

        public static async Task<byte[]> Test()
        {

            Policy<byte[]> policyFallback = Policy<byte[]>.Handle<Exception>()
                //定义降级策略
                .FallbackAsync(async r =>
                {
                    return new byte[5];
                }, async ex => //这个参数也是一个异步委托,可以拿到异常信息
                {
                    Console.WriteLine(ex.Exception.Message);
                });

            //定义超时策略
            Policy policyTimeout = Policy.TimeoutAsync(5, Polly.Timeout.TimeoutStrategy.Pessimistic);

            //超时策略与降级策略的拼接
            Policy<byte[]> policy = policyFallback.WrapAsync(policyTimeout);

            var bytes = policy.ExecuteAsync(async () =>
              {
                  Console.WriteLine("任务开始");

                  using (HttpClient http = new HttpClient())
                  {
                      Thread.Sleep(4000);
                      var result = await http.GetByteArrayAsync("http://static.rupeng.com/upload/chatimage/20183/07EB793A4C247A654B31B4D14EC64BCA.png");
                      return result;
                  }
              });
            return await bytes;
        }
    }
}

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 (可选其他版本)

AspectCore框架的基本使用

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

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

CustomInterceptorAttribute类的定义如下

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

namespace AopTest
{
    //创建一个CustomInterceptorAttribute类,让它继承自AbstractInterceptorAttribute
    public class CustomInterceptorAttribute: AbstractInterceptorAttribute
    {
        //实现AbstractInterceptorAttribute类中的Invoke抽象方法
        public async override Task Invoke(AspectContext context, AspectDelegate next)
        {
            try
            {
                Console.WriteLine("服务调用之前");
                await next(context);//这里是执行被拦截的方法(这里别管是否理解,照着写)
            }
            catch (Exception)
            {
                Console.WriteLine("服务抛出异常");
                throw; //既然抛出异常了。这里就应该抛出异常,否则,主代码中的 Console.WriteLine("程序执行完毕");还会被执行到
            }
            finally
            {
                Console.WriteLine("服务调用之后");
            }
        }

        //AspectContext类型的context属性里面值的含义:
        //Implementation 实际动态创建的Person子类的对象。
        //ImplementationMethod就是Person子类的Say方法
        //Parameters 方法的参数值。
        //Proxy == Implementation:当前场景下
        //ProxyMethod == ImplementationMethod:当前场景下
        //ReturnValue返回值
        //ServiceMethod是Person的Say方法
    }
}

3>创建一个Person类

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

Person类的定义如下

using System;
using System.Collections.Generic;
using System.Text;

namespace AopTest
{
    public class Person
    {
        //要用AspectCore这个AOP框架,这个方法必须是虚方法,然后在方法上打上CustomInterceptor特性标签
        [CustomInterceptor]
        public virtual void Say(string msg)
        {
            Console.WriteLine("服务调用中..." + msg);
        }
    }
}

4>调用

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.Say("你好,中国");
            }
            Console.WriteLine("程序执行完毕");
            Console.ReadKey();         
        }
    }
}

结果如下:

猜你喜欢

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