结合上章所微服务:熔断降级一中所讲的知识点,我们现在来写第一版的降级框架
AspectCore框架的基本使用
1>创建一个.net core的控制台应用程序,我取名叫HystrixTest
2>熔断降级框架 Install-Package Polly -Version 6.0.1(可以安装其他的版本)
3> 安装AspectCore框架服务包:Install-Package AspectCore.Core -Version 0.5.0 (可选其他版本)
4>在控制台应用程序中创建一个HystrixCommandAttribute特性类(名字自取),然后让这个类继承AbstractInterceptorAttribute特性类
HystrixCommandAttribute类的定义如下
using AspectCore.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace HystrixTest
{
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
public HystrixCommandAttribute(string fallBackMethod) //从特性的构造函数中获取降级方法名称
{
this.FallBackMethod = fallBackMethod;
}
public string FallBackMethod { get; set; } //降级方法名称
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
try
{
await next(context);
}
catch
{
//出现异常:如果出现异常则需要调用降级方法
//第一步:获取降级的方法名称(根据对象获取类,从类中获取方法)
//第二步:调用降级方法
//第三步:把降级方法的返回值返回
//获取降级方法
MethodInfo fallbackMethodInfo = context.Implementation.GetType().GetMethod(this.FallBackMethod);
//调用降级方法
object returnValue = fallbackMethodInfo.Invoke(context.Implementation, context.Parameters);
//把降级方法的返回值返回
context.ReturnValue = returnValue; //把降级方法的放回值作为我们的返回值进行返回
}
}
}
}
5>在项目中添加一个Person类
在Person类中定义三的虚方法,注意,一定要是虚方法,例如在Send_YD方法上打上我们上面定义的HystrixCommandAttribute特性标签,这样当我们在调用这个Person类Send_YD方法的时候,就会被HystrixCommandAttribute拦截。(其实它就相当于我们MVC中的过滤器)(应用场景:我们在做微服务的时候,处理服务的熔断降级。例如:我在Person类中定义3个方法A,B,C,A方法中调用移动发送短信,B方法中调用联通发送短信,C方法中调用电信发送短信。正常情况下我们调用的是A方法发送短信,但是如果调用A方法失败的时候我们可以降级到B方法,如果B方法还是失败,我们可以降级到C方法)
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace HystrixTest
{
public class Person
{
//要用AspectCore这个AOP框架,这个方法必须是虚方法,然后在方法上打上HystrixCommand特性标签
[HystrixCommand(nameof(Send_LT))] //这个特性的作用是:如果Send_YD方法调用失败,则降级到Send_LT方法
public async virtual Task Send_YD(string msg)
{
Console.WriteLine("移动短信服务开始调用");
using (HttpClient http = new HttpClient())
{
//这里的实际代码是使用httpClient去调用移动接口服务
var a = 1; var b = 0; var c = a / b;
Console.WriteLine(msg);
}
Console.WriteLine("移动短信服务调用完毕");
return "OK";
}
[HystrixCommand(nameof(Send_DX))] //这个特性的作用是:如果Send_LT方法调用失败,则降级到Send_DX
public async virtual Task Send_LT(string msg)
{
Console.WriteLine("联通短信服务开始调用");
using (HttpClient http = new HttpClient())
{
//这里的实际代码是使用httpClient去调用联通接口服务
Console.WriteLine(msg);
}
Console.WriteLine("联通短信服务调用完毕");
return "OK";
}
public async virtual Task Send_DX(string msg)
{
Console.WriteLine("电信短信服务开始调用");
using (HttpClient http = new HttpClient())
{
//这里的实际代码是使用httpClient去调用电信接口服务
Console.WriteLine(msg);
}
Console.WriteLine("电信短信服务调用完毕");
return "OK";
}
}
}
6>控制台中调用
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.Send_YD("你好,中国");
}
Console.WriteLine("程序执行完毕");
Console.ReadKey();
}
}
}
这是杨中科同志持续维护的开源项目,如果有需要可以直接使用它的开源框架,以下我是把它的源代码放到我的项目中
github最新地址 https://github.com/yangzhongke/RuPeng.HystrixCore
Nuget地址:https://www.nuget.org/packages/RuPeng.HystrixCore
1>创建一个.net core的HystrixTest控制台应用程序
2>熔断降级框架 Install-Package Polly -Version 6.0.1(可以安装其他的版本)
3> 安装AspectCore框架服务包:Install-Package AspectCore.Core -Version 0.5.0 (可选其他版本)
4>安装:Install-Package Microsoft.Extensions.Caching.Memory (.net自带内存缓存)
5>在控制台应用程序中创建一个HystrixCommandAttribute特性类(名字自取),然后让这个类继承AbstractInterceptorAttribute特性类
HystrixCommandAttribute类的定义如下
using AspectCore.DynamicProxy;
using Polly;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace HystrixTest
{
[AttributeUsage(AttributeTargets.Method)]
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
///
/// 最多重试几次,如果为0则不重试
///
public int MaxRetryTimes { get; set; } = 0;
///
/// 重试间隔的毫秒数
///
public int RetryIntervalMilliseconds { get; set; } = 100;
///
/// 是否启用熔断
///
public bool EnableCircuitBreaker { get; set; } = false;
///
/// 熔断前出现允许错误几次
///
public int ExceptionsAllowedBeforeBreaking { get; set; } = 3;
///
/// 熔断多长时间(毫秒)
///
public int MillisecondsOfBreak { get; set; } = 1000;
///
/// 执行超过多少毫秒则认为超时(0表示不检测超时)
///
public int TimeOutMilliseconds { get; set; } = 0;
///
/// 缓存多少毫秒(0表示不缓存),用“类名+方法名+所有参数ToString拼接”做缓存Key
///
public int CacheTTLMilliseconds { get; set; } = 0;
///
/// 降级方法名称
///
public string FallBackMethod { get; set; }
private static ConcurrentDictionary policies = new ConcurrentDictionary();
private static readonly Microsoft.Extensions.Caching.Memory.IMemoryCache memoryCache
= new Microsoft.Extensions.Caching.Memory.MemoryCache(new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions());
public HystrixCommandAttribute(string fallBackMethod)
{
this.FallBackMethod = fallBackMethod;
}
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
//一个HystrixCommand中保持一个policy对象即可
//其实主要是CircuitBreaker要求对于同一段代码要共享一个policy对象
//根据反射原理,同一个方法的MethodInfo是同一个对象,但是对象上取出来的HystrixCommandAttribute
//每次获取的都是不同的对象,因此以MethodInfo为Key保存到policies中,确保一个方法对应一个policy实例
policies.TryGetValue(context.ServiceMethod, out Policy policy);
lock (this)//因为Invoke可能是并发调用,因此要确保policies赋值的线程安全
{
if (policy == null)
{
policy = Policy.NoOpAsync();//创建一个空的Policy
if (EnableCircuitBreaker) //如果启动用了熔断策略
{
//定义一个熔断策略
var policyCircuitBreaker = Policy.Handle().CircuitBreakerAsync(this.ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(this.MillisecondsOfBreak));
policy = policy.WrapAsync(policyCircuitBreaker);
}
if (this.TimeOutMilliseconds > 0) //如果启用了超时策略
{
var policyTimeout = Policy.TimeoutAsync(this.TimeOutMilliseconds, Polly.Timeout.TimeoutStrategy.Pessimistic);
policy = policy.WrapAsync(policyTimeout);
}
if (MaxRetryTimes > 0)//如果启用了重试策略
{
var policyRetry = Policy.Handle().RetryAsync(this.MaxRetryTimes, (e, i) => TimeSpan.FromMilliseconds(this.RetryIntervalMilliseconds));
policy = policy.WrapAsync(policyRetry);
}
Policy policyFallBack = Policy
.Handle()
.FallbackAsync(async (ctx, t) =>
{
//第一步:从AspectContext中拿到当前请求真实的context
//这里拿到的aspectContext就是最下面的那段await policy.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);
//不能如下这样,因为这是闭包相关(我们在前面有一句代码是这样的if (policy == null) 如果policy为空的时候我们给policy赋值了,但是当第二次再来调用该方法的时候,policy已经有值了,如果这样写第二次调用Invoke的时候context指向的还是第一次的对象,所以要通过Polly的上下文来传递AspectContext )
//context.ReturnValue = fallBackResult;
//第四步:把降级方法的返回值返回
aspectContext.ReturnValue = fallBackResult;
}, async (ex, t) =>
{
//这里是捕获异常,ex是异常信息
});
policy = policyFallBack.WrapAsync(policy);
//放入
policies.TryAdd(context.ServiceMethod, policy);
}
}
//把本地调用的AspectContext传递给Polly,主要给FallbackAsync中使用,避免闭包的坑
Context pollyCtx = new Context();//Context是polly中通过Execute给FallBack,Execute等回调方法传上下文对象使用的(它是Polly的上下文)
pollyCtx["aspectContext"] = context; //这个context是aspectCore的上下文,通过这个赋值将AspectCore的上下文传给Polly的上下文
//使用.net自带的缓存: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);
}
}
}
}
6>创建一个Person类
Person类定义如下
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace HystrixTest
{
public class Person
{
//要用AspectCore这个AOP框架,这个方法必须是虚方法,然后在方法上打上HystrixCommand特性标签
[HystrixCommand(nameof(Send_LT),MaxRetryTimes =8,EnableCircuitBreaker =true)] //这个特性的作用是:如果Send_YD方法调用失败,则降级到Send_LT方法,MaxRetryTimes =8表示出现异常最多重试8次,EnableCircuitBreaker =true表示启用熔断策略
public async virtual Task Send_YD(string msg)
{
Console.WriteLine("移动短信服务开始调用");
using (HttpClient http = new HttpClient())
{
//这里的实际代码是使用httpClient去调用移动接口服务
var a = 1; var b = 0; var c = a / b;
Console.WriteLine(c);
}
Console.WriteLine("移动短信服务调用完毕");
return "OK";
}
[HystrixCommand(nameof(Send_DX))] //这个特性的作用是:如果Send_LT方法调用失败,则降级到Send_DX
public async virtual Task Send_LT(string msg)
{
Console.WriteLine("联通短信服务开始调用");
using (HttpClient http = new HttpClient())
{
//这里的实际代码是使用httpClient去调用联通接口服务
Console.WriteLine(msg);
}
Console.WriteLine("联通短信服务调用完毕");
return "OK";
}
public async virtual Task Send_DX(string msg)
{
Console.WriteLine("电信短信服务开始调用");
using (HttpClient http = new HttpClient())
{
//这里的实际代码是使用httpClient去调用电信接口服务
Console.WriteLine(msg);
}
Console.WriteLine("电信短信服务调用完毕");
return "OK";
}
}
}
7>控制台Main中调用
using AspectCore.DynamicProxy;
using System;
namespace HystrixTest
{
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.Send_YD("你好,中国");
}
Console.WriteLine("程序执行完毕");
Console.ReadKey();
}
}
}
在asp.net core项目中,可以借助于asp.net core的依赖注入,简化代理类对象的注入,不用再自己调用ProxyGeneratorBuilder 进行代理类对象的注入了。
1>其他的都跟上面一样,这里我们创建的项目一个.net core的Web项目
2>在Nuget中安装 Install-Package AspectCore.Extensions.DependencyInjection
3>修改Startup类的ConfigureServices方法,将ConfigureServices方法的放回类型修改成IServiceProvider类型,并添加以下代码
services.AddSingleton
//return services.BuildAspectCoreServiceProvider();//过期方法:由下面方法替代
return services.BuildDynamicProxyServiceProvider();//表示由AspectCore来接管依赖注入的功能
public IServiceProvider ConfigureServices(IServiceCollection services)//把这个方法的返回值由原来的void改成IServiceProvider
{
services.AddMvc();
//services.AddSingleton();//表示把Person注入。BuildAspectCoreServiceProvider是让aspectcore接管注入。在Controller中就可以通过构造函数进行依赖注入了 (一般单一的类才这样单独用)
RegisterServices(this.GetType().Assembly, services);// 把当前程序集传递过去,只要程序集中的public类有方法标记HystrixCommandAttribute特性,就将该类进行注册
//return services.BuildAspectCoreServiceProvider();//过期方法:由下面方法替代
return services.BuildDynamicProxyServiceProvider();//表示由AspectCore来接管依赖注入的功能
}
//自定义一个注册类
private static void RegisterServices(Assembly asm, IServiceCollection services)
{
//遍历程序集中的所有public类型
foreach (Type type in asm.GetExportedTypes()) //asm.GetExportedTypes()表示获取此程序集中定义的公共类型,这些公共类型在程序集外可见
{
//判断类中是否有标注了HystrixCommandAttribute的方法
bool hasHystrixCommandAttr = type.GetMethods()
.Any(m => m.GetCustomAttribute(typeof(HystrixCommandAttribute)) != null);
if (hasHystrixCommandAttr)
{
services.AddSingleton(type);
}
}
}
4>调用:新建一个Webapi控制器类,在控制器中进行依赖注入Person对象
namespace HystrixTest2
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
private Person p;
public ValuesController(Person per)//构造函数注入Person对象
{
this.p = per;
}
// GET: api/
[HttpGet]
public async Task> Get()
{
//通过AspectCore创建代理对象
//ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
//using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build())
//{
// //必须要通过这种方式拿到Person类对象(其实这里是拿到Person类的子类对象,这个子类名称虽然也叫Person,但是它与我们声明的Person类不在同一个命名空间下,这子类重写了父类Persond的Say方法,这就是为什么Pserson类中的Say方法必须是虚方法的原因)
// Person p = proxyGenerator.CreateClassProxy();
// var a= await p.Send_YD("你好,中国");
//}
var a = await p.Send_YD("你好");
return new string[] { "value1", "value2" };
}
}
}