1:、 什么是熔断降级
熔断就是“保险丝”。当出现某些状况时,切断服务,从而防止应用程序不断地尝试执 行可能会失败的操作给系统造成“雪崩”,或者大量的超时等待导致系统卡死。
2:降级的目的
降级的目的是当某个服务提供者发生故障的时候,向调用方返回一个错误响应或者替代 响应。举例子:调用联通接口服务器发送短信失败之后,改用移动短信服务器发送,如果移 动短信服务器也失败,则改用电信短信服务器,如果还失败,则返回“失败”响应;在从推 荐商品服务器加载数据的时候,如果失败,则改用从缓存中加载,如果缓存中也加载失败, 则返回一些本地替代数据。
一:Polly 简单使用
建立一个NETCore控制台项目:安装NuGet包:Install-Package Polly -Version 6.0.1
在Program.cs中添加一下代码,每一个部分分为一种方法
启动项目的方法最好用cmd命令执行也可实例调试,命令执行:dotnet PollyTest.dll(PollyTest.dll代表该项目bin目录下的程序dll);
using Polly;
using System;
namespace PollyTest
{
class Program
{
static void Main(string[] args)
{
#region 异常捕获 第一种方法
//实例化
Policy policy = Policy
.Handle<ArgumentException>() //故障
.Fallback(() =>//动作
{
Console.WriteLine("执行出错");
});
policy.Execute(() =>
{//在策略中执行业务代码
//这里是可能会产生问题的业务系统代码
Console.WriteLine("开始任务");
throw new ArgumentException("Hello world!");
Console.WriteLine("完成任务");
});
#endregion
#region 重载异常 第二种方法
//如果没有被Handle处理的异常,则会导致未处理异常被抛出。还可以用Fallback的其他重载获取异常信息:
Policy policy = Policy
.Handle<ArgumentException>() //故障
.Fallback(() =>//动作
{
Console.WriteLine("执行出错");
}, ex =>
{
Console.WriteLine(ex);
});
policy.Execute(() =>
{
Console.WriteLine("开始任务");
throw new ArgumentException("Hello world!");
Console.WriteLine("完成任务");
});
#endregion
#region 返回值 第三种方法
//如果Execute中的代码是带返回值的,那么只要使用带泛型的Policy<T> 类即可:
Policy<string> policy = Policy<string>
.Handle<Exception>() //故障
.Fallback(() =>//动作
{
Console.WriteLine("执行出错");
return "降级的值";
});
string value = policy.Execute(() =>
{
Console.WriteLine("开始任务");
throw new Exception("Hello world!");
Console.WriteLine("完成任务");
return "正常的值";
});
#endregion
Console.ReadKey();
}
}
}
重试处理
using Polly;
using System;
namespace PollyTest
{
class Program
{
static void Main(string[] args)
{
#region 永久重试
//RetryForever()是一直重试直到成功
//Retry()是重试最多一次;
//Retry(n) 是重试最多n次;
//WaitAndRetry()可以实现“如果出错等待100ms再试还不行再等150ms秒。
Policy policy = Policy
.Handle<Exception>()
.Retry(3);
policy.Execute(() =>
{
Console.WriteLine("开始任务");
if (DateTime.Now.Second % 10 != 0)
{
throw new Exception("出错");
}
Console.WriteLine("完成任务");
});
#endregion
Console.ReadKey();
}
}
}
短路保护 Circuit Breaker
出现N次连续错误,则把“熔断器”(保险丝)熔断,等待一段时间,等待这段时间内如果再Execute 则直接抛出BrokenCircuitException异常,根本不会再去尝试调用业务代码。等待时间过去之后,再 执行Execute的时候如果又错了(一次就够了),那么继续熔断一段时间,否则就恢复正常。 这样就避免一个服务已经不可用了,还是使劲的请求给系统造成更大压力。
using Polly;
using System;
namespace PollyTest
{
class Program
{
static void Main(string[] args)
{
#region 短路保护
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)
{
Console.WriteLine("execute出错" + ex);
}
Thread.Sleep(500);
}
Console.ReadKey();
#endregion
}
}
}
策略封装
可以把多个ISyncPolicy合并到一起执行: policy3= policy1.Wrap(policy2);
执行policy3就会把policy1、policy2封装到一起执行
policy9=Policy.Wrap(policy1, policy2, policy3, policy4, policy5);把更多一起封装。
超时处理
#region 超时处理
//下面代码实现了“出现异常则重试三次,如果还出错就FallBack”
Policy policyRetry = Policy.Handle<Exception>()
.Retry(3);
Policy policyFallback = Policy.Handle<Exception>()
.Fallback(() =>
{
Console.WriteLine("降级");
});
//Wrap:包裹。policyRetry在里面,policyFallback裹在外面。
//如果里面出现了故障,则把故障抛出来给外面
Policy policy = policyFallback.Wrap(policyRetry);
policy.Execute(() =>
{
Console.WriteLine("开始任务");
if (DateTime.Now.Second % 10 != 0)
{
throw new Exception("出错");
}
Console.WriteLine("完成任务");
});
#endregion
二: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
在该控制台项目中添加Nuget包,并且新建一个CustomInterceptorAttribute类
using AspectCore.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace PollyTest
{
//安装NuGet包AspectCore.Core 如果vs是2.1版本则包要选择1.2.0 vs是3.0则直接安装即可
public class CustomInterceptorAttribute : AbstractInterceptorAttribute
{
//每个被拦截的方法中执行
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
try
{
Console.WriteLine("Before service call");
await next(context);//执行被拦截的方法
}
catch (Exception)
{
Console.WriteLine("Service threw an exception!");
throw;
}
finally
{
Console.WriteLine("After service call");
}
}
}
}
在该控制台项目中新建一个被代理拦截的Person类
public class Person
{
[CustomInterceptor]
public virtual void Say(string msg)
{
Console.WriteLine("service calling..." + msg);
}
}
在该项目下的程序入口Program类中写入代码进行测试
static void Main(string[] args)
{
//创建代理
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
Person p = proxyGenerator.CreateClassProxy<Person>();
p.Say("rupeng.com");
}
}
三:创建简单的熔断降级框架
熔断降级初级框架实现
1:在该控制台项目中新建一个HystrixCommandAttribute类
[AttributeUsage(AttributeTargets.Method)]
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
#region 初级框架
public HystrixCommandAttribute(string fallBackMethod)
{
this.FallBackMethod = fallBackMethod;
}
public string FallBackMethod { get; set; }
public override async Task Invoke(AspectContext context, AspectDelegate next)
{
try
{
await next(context);//执行被拦截的方法
}
catch (Exception ex)
{
//context.ServiceMethod 被拦截的方法。context.ServiceMethod.DeclaringType被拦截方法所在的类
//context.Implementation 实际执行的对象 p
//context.Parameters 方法参数值
//如果执行失败,则执行 FallBackMethod
var fallBackMethod =
context.ServiceMethod.DeclaringType.GetMethod(this.FallBackMethod);
//反射调用方法 反射的方法Invoke();
Object fallBackResult = fallBackMethod.Invoke(context.Implementation,
context.Parameters);
context.ReturnValue = fallBackResult;
}
}
#endregion
}
2:将该控制台项目中Person类中添加方法(Person类没有就新建,因为这里已经创建过了是直接在上述的基础上进行添加)
public class Person
{
[HystrixCommand("TengXunBackAsync")] //TengXunBackAsync表示如果小米短信失败调的下一个方法名称
public virtual async Task<string> XiaoMiAsync(string name)
{
throw new Exception("小米短信出现异常!");//自定义抛出一个异常 测试小米失败是否会跳到腾讯短信
Console.WriteLine("小米短信" + name);
return "ok";
}
[HystrixCommand("HuaWeiBackAsync")]
public virtual async Task<string> TengXunBackAsync(string name)
{
throw new Exception("腾讯短信出现异常!");//自定义抛出一个异常 测试腾讯失败是否会跳到华为短信
Console.WriteLine("腾讯短信" + name);
return "ok";
}
public async Task<string> HuaWeiBackAsync(string name)
{
//如果该方法也想在出现异常时调用另一个方法 那本方法就要加一个virtual的修饰符
Console.WriteLine("华为短信" + name);
return "ok";
}
}
3:在程序入口Program.cs中进行测试
//创建代理
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
Person p = proxyGenerator.CreateClassProxy<Person>();
p.XiaoMiAsync("rupeng.com");
}
四:细化框架
在HystrixCommandAttribute类中添加细化初级框架的代码
添加NuGet包Microsoft.Extensions.Caching.Memory
HystrixCommandAttribute类如下:
[AttributeUsage(AttributeTargets.Method)]
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
#region 细化初级框架
/// <summary>
/// 最多重试几次,如果为0则不重试
/// </summary>
public int MaxRetryTimes { get; set; } = 0;
/// <summary>
/// 重试间隔的毫秒数
/// </summary>
public int RetryIntervalMilliseconds { get; set; } = 100;
/// <summary>
/// 是否启用熔断
/// </summary>
public bool EnableCircuitBreaker { get; set; } = false;
/// <summary>
/// 熔断前出现允许错误几次
/// </summary>
public int ExceptionsAllowedBeforeBreaking { get; set; } = 3;
/// <summary>
/// 熔断多长时间(毫秒)
/// </summary>
public int MillisecondsOfBreak { get; set; } = 1000;
/// <summary>
/// 执行超过多少毫秒则认为超时(0表示不检测超时)
/// </summary>
public int TimeOutMilliseconds { get; set; } = 0;
/// <summary>
/// 缓存多少毫秒(0表示不缓存),用“类名+方法名+所有参数ToString拼接”做缓存Key
/// </summary>
public int CacheTTLMilliseconds { get; set; } = 0;
private IAsyncPolicy policy;
//NuGet包Microsoft.Extensions.Caching.Memory
private static readonly Microsoft.Extensions.Caching.Memory.IMemoryCache memoryCache
= new Microsoft.Extensions.Caching.Memory.MemoryCache(new
Microsoft.Extensions.Caching.Memory.MemoryCacheOptions());
/// <summary>
///构造函数
/// </summary>
/// <param name="fallBackMethod">降级的方法名</param>
public HystrixCommandAttribute(string fallBackMethod, int maxRetryTimes)
{
this.FallBackMethod = fallBackMethod;
this.MaxRetryTimes = maxRetryTimes;
}
/// <summary>
///构造函数重载
/// </summary>
/// <param name="fallBackMethod">降级的方法名</param>
public HystrixCommandAttribute(string fallBackMethod)
{
this.FallBackMethod = fallBackMethod;
}
public string FallBackMethod { get; set; }
public override async Task Invoke(AspectContext context, AspectDelegate next)
{
//一个HystrixCommand中保持一个policy对象即可
//其实主要是CircuitBreaker要求对于同一段代码要共享一个policy对象
//根据反射原理,同一个方法就对应一个HystrixCommandAttribute,无论几次调用,
//而不同方法对应不同的HystrixCommandAttribute对象,天然的一个policy对象共享
//因为同一个方法共享一个policy,因此这个CircuitBreaker是针对所有请求的。
//Attribute也不会在运行时再去改变属性的值,共享同一个policy对象也没问题
lock (this)//因为Invoke可能是并发调用,因此要确保policy赋值的线程安全
{
if (policy == null)
{
policy = Policy.NoOpAsync();//创建一个空的Policy
//如果启用熔断
if (EnableCircuitBreaker)
{
//策略封装
policy = policy.WrapAsync(Policy.Handle<Exception>().CircuitBreakerAsync(ExceptionsAllowedBeforeBreaking,
TimeSpan.FromMilliseconds(MillisecondsOfBreak)));
}
//超时时间
if (TimeOutMilliseconds > 0)
{
//策略封装执行超时
policy = policy.WrapAsync(Policy.TimeoutAsync(() =>
TimeSpan.FromMilliseconds(TimeOutMilliseconds), Polly.Timeout.TimeoutStrategy.Pessimistic));
}
//最大重试次数
if (MaxRetryTimes > 0)
{
//策略封装最多试几次
policy = policy.WrapAsync(Policy.Handle<Exception>().WaitAndRetryAsync(MaxRetryTimes, i =>
TimeSpan.FromMilliseconds(RetryIntervalMilliseconds)));
}
IAsyncPolicy policyFallBack = Policy
.Handle<Exception>()
.FallbackAsync(async (ctx, t) =>
{
//这里拿到的就是ExecuteAsync(ctx => next(context), pollyCtx);这里传的pollyCtx
AspectContext aspectContext = (AspectContext)ctx["aspectContext"];
//获取降级的方法
var fallBackMethod = context.ServiceMethod.DeclaringType.GetMethod(this.FallBackMethod);
//调用降级的方法
Object fallBackResult = fallBackMethod.Invoke(context.Implementation, context.Parameters);
//不能如下这样,因为这是闭包相关,如果这样写第二次调用Invoke的时候context指向的
//还是第一次的对象,所以要通过Polly的上下文来传递AspectContext
//context.ReturnValue = fallBackResult;
aspectContext.ReturnValue = fallBackResult;
}, async (ex, t) => { });
policy = policyFallBack.WrapAsync(policy);
}
}
//把本地调用的AspectContext传递给Polly,主要给FallbackAsync中使用,避免闭包的坑
Context pollyCtx = new Context();//Context是polly中通过Execute给FallBack、Execute等回调方法传上下文对象使用的
pollyCtx["aspectContext"] = context;//context是aspectCore的上下文
//Install-Package Microsoft.Extensions.Caching.Memory
if (CacheTTLMilliseconds > 0)
{
//用类名+方法名+参数的下划线连接起来作为缓存key
string cacheKey = "HystrixMethodCacheManager_Key_" +
context.ServiceMethod.DeclaringType
+ "." +
context.ServiceMethod + string.Join("_", context.Parameters);
//尝试去缓存中获取。如果找到了,则直接用缓存中的值做返回值
if (memoryCache.TryGetValue(cacheKey, out var cacheValue))
{
context.ReturnValue = cacheValue;
}
else
{
//如果缓存中没有,则执行实际被拦截的方法
await policy.ExecuteAsync(ctx => next(context), pollyCtx);
//存入缓存中
using (var cacheEntry = memoryCache.CreateEntry(cacheKey))
{
cacheEntry.Value = context.ReturnValue;//返回值放入缓存
cacheEntry.AbsoluteExpiration = DateTime.Now +
TimeSpan.FromMilliseconds(CacheTTLMilliseconds);
}
}
}
else//如果没有启用缓存,就直接执行业务方法
{
await policy.ExecuteAsync(ctx => next(context), pollyCtx);
}
}
#endregion
}
将该控制台项目中Person类中修改方法
public class Person
{
[HystrixCommand("TengXunBackAsync",3)]
//("TengXunBackAsync",3)第一个参数表示如果小米短信失败调的下一个方法,第二个表示如果失败重试的次数
//更多参数属性的使用效果可参照该项目HystrixCommandAttribute类中属性
public virtual async Task<string> XiaoMiAsync(string name)
{
Console.WriteLine("小米短信");
throw new Exception("小米短信出现异常!");//如果在规定的请求数类全部失败则会进入下一个方法 本次测试时测试三次重试
Console.WriteLine("小米短信" + name);
return "ok";
}
[HystrixCommand("HuaWeiBackAsync",3)]
public virtual async Task<string> TengXunBackAsync(string name)
{
Console.WriteLine("腾讯短信");
throw new Exception("腾讯短信出现异常!");
Console.WriteLine("腾讯短信" + name);
return "ok";
}
public async Task<string> HuaWeiBackAsync(string name)
{
//如果该方法也想在出现异常时调用另一个方法 那本方法就要加一个virtual的修饰符
Console.WriteLine("华为短信" + name);
return "ok";
}
}
最后Program.cs中进行测试
//创建代理
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
Person p = proxyGenerator.CreateClassProxy<Person>();
p.XiaoMiAsync("rupeng.com");
}
Console.ReadKey();