当我们在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这一些都是用一些无意义的代码模拟出来。
出现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(); } } }要求懂的知识: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(); } } }结果如下: