当我们在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这一些都是用一些无意义的代码模拟出来。
using Polly;
using Polly.Fallback;
using System;
namespace PollyFrame
{
class Program
{
static void Main(string[] args)
{
try
{
Policy policy = Policy.Handle()//这就是“故障”
//程序出现故障的时候,会执行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就没办法捕获了)");
}
}
}
}
using Polly;
using Polly.Fallback;
using System;
namespace PollyFrame
{
class Program
{
static void Main(string[] args)
{
try
{
//关于这个Handle方法有很多重载,比如下面这个方法就规定,只有出现AggregateException错误,并且错误消息是“数据错误”或者有DivideByZeroException异常的时候时候才处理
//Policy policy = Policy.Handle(ex=>ex.Message=="数据错误").Or()
Policy policy = Policy.Handle()//这就是“故障” 程序出现问题的时候,会执行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()
//.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()
.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()
.Retry(3);
//定义个降级策略
Policy policyFallback = Policy.Handle()
.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()
.Fallback(() =>
{
Console.WriteLine("执行降级代码进行降级");
});
Policy policy = policyFallBack.Wrap(policyTimeout);
policy.Execute(() =>
{
Console.WriteLine("开始执行代码");
Thread.Sleep(5000);
Console.WriteLine("完成任务");
});
Console.ReadKey();
}
}
}
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()
.Fallback(() =>
{
Console.WriteLine("执行降级代码进行降级");
});
//定义一个重试策略
Policy policyRetry = Policy.Handle().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();
}
}
}
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 Test()
{
Policy policyFallback = Policy.Handle()
//定义降级策略
.FallbackAsync(async r =>
{
return new byte[5];
}, async ex => //这个参数也是一个异步委托,可以拿到异常信息
{
Console.WriteLine(ex.Exception.Message);
});
//定义超时策略
Policy policyTimeout = Policy.TimeoutAsync(5, Polly.Timeout.TimeoutStrategy.Pessimistic);
//超时策略与降级策略的拼接
Policy 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、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();
p.Say("你好,中国");
}
Console.WriteLine("程序执行完毕");
Console.ReadKey();
}
}
}
结果如下: