1、熔断的定义:
熔断就是“保险丝”。当出现某些状况时,切断服务,从而防止应用程序不断地尝试执
行可能会失败的操作给系统造成“雪崩”,或者大量的超时等待导致系统卡死。
2、降级目的:
降级的目的是当某个服务提供者发生故障的时候,向调用方返回一个错误响应或者替代
响应。举例子:调用联通接口服务器发送短信失败之后,改用移动短信服务器发送,如果移
动短信服务器也失败,则改用电信短信服务器,如果还失败,则返回“失败”响应;在从推
荐商品服务器加载数据的时候,如果失败,则改用从缓存中加载,如果缓存中也加载失败,
则返回一些本地替代数据。
一、创建一个PollyTest(.netcore)控制台项目
1.1、在该项目中添加Nuget包(Polly)
1.2、在Program类中进行测试
1.2.1 异常捕获
//实例化
Policy policy = Policy
.Handle<ArgumentException>() //故障
.Fallback(() =>//动作
{
Console.WriteLine("执行出错");
});
policy.Execute(() =>
{
//在策略中执行业务代码//这里是可能会产生问题的业务系统代码
Console.WriteLine("开始任务");
throw new ArgumentException("Hello world!");
Console.WriteLine("完成任务");
});
Console.ReadKey();
1.2.2 重载异常
//实例化
Policy policy = Policy
.Handle<ArgumentException>() //故障
.Fallback(() =>//动作
{
Console.WriteLine("执行出错");
}, ex =>
{
Console.WriteLine(ex);
});
policy.Execute(() =>
{
Console.WriteLine("开始任务");
throw new ArgumentException("Hello world!");
Console.WriteLine("完成任务");
});
Console.ReadKey();
1.2.3 带返回值
//实例化
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 "正常的值";
});
Console.WriteLine("返回值:" + value);
1.2.4 永久重试
//实例化
Policy policy = Policy
.Handle<Exception>()
.RetryForever();
policy.Execute(() =>
{
Console.WriteLine("开始任务");
if (DateTime.Now.Second % 10 != 0)
{
throw new Exception("出错");
}
Console.WriteLine("完成任务");
});
1.2.5 短路保护
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);
}
1.2.6 超时处理
Policy policy = Policy
.Handle<Exception>() //定义所处理的故障
.Fallback(() =>
{
Console.WriteLine("执行出错");
});
policy = policy.Wrap(Policy.Timeout(2, TimeoutStrategy.Pessimistic));
policy.Execute(() =>
{
Console.WriteLine("开始任务");
Thread.Sleep(5000);
Console.WriteLine("完成任务");
});
二、AOP 框架基础
2.1、在PollyTest项目中添加Nuget包(AspectCore.Core)
2.2、在PollyTest项目中新建一个编写拦截器CustomInterceptorAttribute类
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");
}
}
}
2.3、在PollyTest项目中新建一个需要被代理拦截的Person类
public class Person
{
[CustomInterceptor]
public virtual void Say(string msg)
{
Console.WriteLine("service calling..." + msg);
}
}
2.4、在PollyTest项目下Program类中创建代理对象
//通过 AspectCore 创建代理对象
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
Person p = proxyGenerator.CreateClassProxy<Person>();
p.Say("rupeng.com");
}
Console.ReadKey();
三、创建简单的熔断降级框架
3.1、在PollyTest项目下Person类中添加代码
//创建简单的熔断降级框架
[HystrixCommand("TengXunAsync")]
public virtual async Task<string> XiaoMiAsync(string name)
{
throw new Exception("周瑜!");
Console.WriteLine("小米短信" + name);
return "fail";
}
[HystrixCommand("HuaWeiAsync")]
public virtual async Task<string> TengXunAsync(string name)
{
throw new Exception("周瑜!");
Console.WriteLine("腾讯短信" + name);
return "ok";
}
public async Task<string> HuaWeiAsync(string name)
{
Console.WriteLine("华为短信" + name);
return "ok";
}
3.2、在PollyTest项目新建一个HystrixCommandAttribute类
[AttributeUsage(AttributeTargets.Method)]
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
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);
//反射调用方法
Object fallBackResult = fallBackMethod.Invoke(context.Implementation,
context.Parameters);
context.ReturnValue = fallBackResult;
}
}
}
3.3、在PollyTest项目下Program类中创建代理对象
//通过 AspectCore 创建代理对象
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
{
Person p = proxyGenerator.CreateClassProxy<Person>();
p.XiaoMiAsync("Hello");
}
Console.ReadKey();
四、细化框架
4.1、在PollyTest项目中添加Nuget包(Microsoft.Extensions.Caching.Memory)
4.2、在PollyTest项目下HystrixCommandAttribute类中添加代码
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
public HystrixCommandAttribute(string fallBackMethod)
{
this.FallBackMethod = fallBackMethod;//失败要调用的方法
}
public HystrixCommandAttribute(string fallBackMethod,int MaxRetryTimes)
{
this.FallBackMethod = fallBackMethod;//失败要调用的方法
this.MaxRetryTimes = MaxRetryTimes;
}
public string FallBackMethod { get; set; }
/// <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;
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)
//{
// 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);
}
}
//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);
// //反射调用方法
// Object fallBackResult = fallBackMethod.Invoke(context.Implementation,
// context.Parameters);
// context.ReturnValue = fallBackResult;
// }
//}
}
4.3、在PollyTest项目下Person类中添加代码
//创建简单的熔断降级框架
[HystrixCommand("TengXunAsync",3)]
public virtual async Task<string> XiaoMiAsync(string name)
{
Console.WriteLine("开始发送小米短信"+name);
throw new Exception("周瑜!");
Console.WriteLine("小米短信" + name);
return "fail";
}
[HystrixCommand("HuaWeiAsync",3)]
public virtual async Task<string> TengXunAsync(string name)
{
Console.WriteLine("开始发送腾讯短信" + name);
throw new Exception("周瑜!");
Console.WriteLine("腾讯短信" + name);
return "ok";
}
public async Task<string> HuaWeiAsync(string name)
{
Console.WriteLine("开始发送华为短信" + name);
return "ok";
}