首页 资讯 社群 我的社区 搜索

三分钟学会.NET微服务之Polly

LM123
2019-03-01 15:44:38

熔断降级是一个非常重要的概念,我们先说一下什么是熔断降级,咱们都知道服务发现,一个有问题的服务器没来得急注销过一会就崩溃掉了,那么我们的请求就有可能访问一个已经崩溃的服务器,那么就会请求失败,因为已经game over了。那么这个问题怎么解决呢,你一定要承认,这个问题是无法避免的。没有什么方法说,我拿到的服务器都没有问题,这事是不可能的,所以你要承认你会有机会拿到有问题的服务器。那么熔断降级就是来解决这种问题的。

一.什么是熔断

熔断就像是“保险丝”,当出现梦中状况时,切断服务,从而防止应用程序不断地尝试造成雪崩,这和我们农村的保险丝很像,天气热了防止火灾,那保险丝会自动断开,防止更大的损失。

降级的目的是当某个服务提供者发生故障的时候,向调用方返回一个错误响应替代响应。

比如我们要做一个双11活动的系统,那么比如一个抽奖的模块崩溃,这个时候呢广大客户端疯狂F5,就会导致整个集群雪崩,这个时候我们就应该中断。

还有一栗子,比如说电信和联通,它们在稳定,有也不稳定的时候,那么如果我们用它们的接口,如果电信崩了,我们就是用联通,这种操作的行为就叫做熔断降级。

其使用场景呢,例如,我们要展示商品信息,我们先从数据库读取,读取失败了,我们就通过cache/redis,如果再失败,我们通过固定的Javascript object 来绑定,如果再失败那就返回一个错误的信息。这就是完美的降低了错误。

这种错误的概率就犹如0.01*way³ 就是1000次才会出现的概率。那如果是不熔断降级 就是100.以上一些栗子,好好读读即可。

二.Polly介绍

.Net Core 中有一个被.Net基金会认可的k库,可以用来进行熔断降级,主要功能:1.重试(retry);2.断路器(circuit-breaker);3.超时检测(timeout);4.缓存(cache);5.降级(fallback)

官方:https://github.com/app-vnext/polly nuget: install-package Polly-Version 6.0.1

三.使用

创建项目与安装库,为了稳定还是选择6.0.1吧。

使用Policy的静态方法创建ISyncPolicy实现类对象,创建方法j既有同步方法也有异步方法,根据自己的需求来选择,下面先演示同步方法,异步的方法也类似。

static void Main(string[] args)
        {
            //handle 当发生argumentException的异常
            Policy policy = Policy.Handle<ArgumentException>()
                .Fallback(() =>
                {
                    //就干什么事情
                    Console.WriteLine("出错了");
                });
       //有可能异常的时候
            policy.Execute(() =>
            {
                Console.WriteLine("开始执行");
                throw new ArgumentException();
                Console.WriteLine("执行结束");
            });
        }

我们运行一下,是如下结果。

但如果书我们故意写一个抛出异常,我们稍微修改一下代码。

policy.Execute(() =>
            {
                Console.WriteLine("开始执行");
                throw new ArgumentException();
                Console.WriteLine("执行结束");
            });

上面呢,我么捕捉的是ArgumentException异常,那么我们如果是报的其他的错误应该会怎样呢?

policy.Execute(() =>
            {
                Console.WriteLine("开始执行");
                throw new Exception();
                Console.WriteLine("执行结束");
            });

FallBack中有很多不同的重载,我们可以根据重载获取不同的报错信息,以下是所有的方法。

我们可以简单的去获取一个对象,代码如下:

 Policy policy = Policy.Handle<ArgumentException>()
                .Fallback(() =>
                {
                    //就干什么事情
                    Console.WriteLine("出错了");
                },ex=> {
                    Console.WriteLine(ex.Message);
                });
            policy.Execute(() =>
            {
                Console.WriteLine("开始执行");
                throw new ArgumentException();
                Console.WriteLine("执行结束");
            });

因为咱们的业务逻辑啊有可能是带返回值的,也有可能是不带返回值的。那如果你的业务逻辑是带返回值的,你就得用一个Policy带参的泛型来创建,那么这个Policy也是泛型的,在FallBack中也要 提一个替代值,因为毕竟是降级嘛,肯定要有一个值来进行替代。

Policy<string> policy = Policy<string>.Handle<Exception>()
                .Fallback(() =>
                {
                    return "降级后的值";
                });
           string value = policy.Execute(() => {
                return "正常值";
            });

四.重试处理

polly提供了重试处理机制,那么这个RetryForever()的场景不可能会出现,我也不知道它是处于什么个操作。我觉得这违背了熔断降级,这不可能让它重试的,那么以下就是用法,但是这根本用不着~

还是推荐使用Retry吧。Retry中可以写个int值进去,就是重试的次数,这个还是不错的!

Policy policy = Policy.Handle<Exception>().RetryForever();
            policy.Execute(() => {
                Console.WriteLine("play task!!");
                if (DateTime.Now.Second % 10 != 0)
                {
                    throw new Exception();
                }
                Console.WriteLine("完成任务!");
            });

不过还是有比较正常点的方法,例如WaitAndRetry这个方法,等等再重试。这个很不错!这个方法里的重载非常之多。

Policy policy = Policy.Handle<Exception>().WaitAndRetry(100, i => TimeSpan.FromMinutes(100));
            policy.Execute(() => {
                Console.WriteLine("play task!!");
                if (DateTime.Now.Second % 10 != 0)
                {
                    throw new Exception();
                }
                Console.WriteLine("完成任务!");
            });

五.短路保护Circuit Breaker

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

这样就避免了一个服务不可用了还在使劲的请求。

 Policy policy = Policy
            .Handle<Exception>()
            .CircuitBreaker(3, TimeSpan.FromSeconds(5));//连续出错3次之后熔断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);
            }

这就像刚才我们说的,我设置了连续3次熔断,那么如果连续3次报错,那么直接不再执行以后的内容,这无疑是非常不错的机制。保证了服务器的性能丢失和不起眼的问题。

六.策略封装与超时处理

策略封装使用的方法是Policy提供的Wrap方法,英译叫做包裹,那么从单词的意思就知道,可以通过策略包裹策略来进行封装,即里面的不行,就走外面的。

Policy policyRetry = Policy.Handle<Exception>()
                .Retry(3);
            Policy policyFallback = Policy.Handle<Exception>()
                .Fallback(() =>
                {
                    Console.WriteLine("降级");
                });
            Policy policy = policyFallback.Wrap(policyRetry);
            policy.Execute(() =>
            {
                Console.WriteLine("play task!!");
                if (DateTime.Now.Second % 10 != 0)
                {
                    throw new Exception();
                }
                Console.WriteLine("完成任务!");
            });

注意这个wrap的包裹顺序的,外在后,内在前。再通过一个超时处理就可以对消耗时间够长的请求进行GG了。

那么你就可以通过超时处理来对我们文章开头的诉说进行一个非常生动形象的通过代码来宣誓。下面说明超时异常的说明

Policy policytimeout = Policy.Timeout(3, TimeoutStrategy.Pessimistic);
            Policy policyFallBack = Policy.Handle<TimeoutRejectedException>()
                .Fallback(() =>
                {
                    Console.WriteLine("熔断降级");
                });
            Policy policy = policyFallBack.Wrap(policytimeout);
            policy.Execute(() =>
            {
                Console.WriteLine("完成任务");
                Thread.Sleep(5000);
                Console.WriteLine("完成任务");
            });
            Console.ReadKey();

这玩腻的用途不过就是:请求网络接口,避免接口长期没有响应造成系统卡死。

七.Polly的异步

     Test1().Wait(); //调用

        static async Task Test1()
        {

            Policy<byte[]> policy = Policy<byte[]>
           .Handle<Exception>()
           .FallbackAsync(async c => {
               Console.WriteLine("执行出错");
               return new byte[0];
           }, async r => {
               Console.WriteLine(r.Exception);
           });
            policy = policy.WrapAsync(Policy.TimeoutAsync(20, TimeoutStrategy.Pessimistic,
            async (context, timespan, task) =>
            {
                Console.WriteLine("timeout");
            }));
            var bytes = await policy.ExecuteAsync(async () =>
            {
                Console.WriteLine("开始任务");
                HttpClient httpClient = new HttpClient();
                var result = await httpClient.GetByteArrayAsync("https://www.cnblogs.com/images/logo_small.gif");
                Console.WriteLine("完成任务");
                return result;
            });
            Console.WriteLine("bytes长度" + bytes.Length);
        }

使用Polly的异步,那么所有的方法都必须是异步,除了Handle方法,因为handle就不需要异步,也没有返回值。通过异步呢,所有的重载方法都构造了一遍,还是可以继续用的。那么这段代码的意思是,通过异步的方式如果我通过httpclient获取某站点的图片的base值,如果在此期间我定义了一个policy,抓住一个异常,如果说两秒之内还没有反应我就超时。直接终止。测试的时候 你可以把值 改变下。

八.最后

相信你跟着我写到现在,已经感到这代码已经非常恶心了,代码中有非常多冗余的代码,近期会使用AspectCore这个AOP框架,听别人说这个库不错,这和Spring cloud的Hystrix差不多。

用户评论