微服务二:熔断降级一

xiaoxiao2025-06-20  10

一、 什么是熔断降级

当我们在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(); } } }

结果如下:

转载请注明原文地址: https://www.6miu.com/read-5032181.html

最新回复(0)