(7)學習筆記 ) ASP.NET CORE微服務 Micro-Service ---- 利用Polly+AOP+依賴註入封裝的降級框架
創建簡單的熔斷降級框架
要達到的目標是: 參與降級的方法參數要一樣,當HelloAsync執行出錯的時候執行HelloFallBackAsync方法。
public class Person
{
[HystrixCommand("HelloFallBackAsync")]
public virtual async Task<string> HelloAsync(string name)
{
Console.WriteLine("hello"+name);
return "ok";
}
public async Task<string> HelloFallBackAsync(string name)
{
Console.WriteLine("執行失敗"+name);
return "fail";
}
}
1、編寫 HystrixCommandAttribute
using AspectCore.DynamicProxy;
using System;
using System.Threading.Tasks;
namespace hystrixtest1
{
//限制這個特性只能標註到方法上
[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
//調用降級方法
//1.調用降級的方法(根據對象獲取類,從類獲取方法)
var fallBackMethod = context.ServiceMethod.DeclaringType.GetMethod(this.FallBackMethod);
//2.調用降級的方法
Object fallBackResult = fallBackMethod.Invoke(context.Implementation, context.Parameters);
//3.把降級方法的返回值返回
context.ReturnValue = fallBackResult;
}
}
}
}
2、編寫類
public class Person//需要public類
{
[HystrixCommand(nameof(HelloFallBackAsync))]
public virtual async Task<string> HelloAsync(string name)//需要是虛方法
{
Console.WriteLine("hello"+name);
String s = null;
// s.ToString();
return "ok";
}
public async Task<string> HelloFallBackAsync(string name)
{
Console.WriteLine("執行失敗"+name);
return "fail";
}
[HystrixCommand(nameof(AddFall))]
public virtual int Add(int i,int j)
{
String s = null;
//s.ToArray();
return i + j;
}
public int AddFall(int i, int j)
{
return 0;
}
}
3、創建代理對象
ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder();
using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build()) { Person p = proxyGenerator.CreateClassProxy<Person>(); Console.WriteLine(p.HelloAsync("yzk").Result); Console.WriteLine(p.Add(1, 2)); }
上面的代碼還支持多次降級,方法上標註[HystrixCommand]並且virtual即可:
public class Person//需要public類
{
[HystrixCommand(nameof(Hello1FallBackAsync))]
public virtual async Task<string> HelloAsync(string name)//需要是虛方法
{
Console.WriteLine("hello" + name);
String s = null;
s.ToString();
return "ok";
}
[HystrixCommand(nameof(Hello2FallBackAsync))]
public virtual async Task<string> Hello1FallBackAsync(string name)
{
Console.WriteLine("Hello降級1" + name);
String s = null;
s.ToString();
return "fail_1";
}
public virtual async Task<string> Hello2FallBackAsync(string name)
{
Console.WriteLine("Hello降級2" + name);
return "fail_2";
}
[HystrixCommand(nameof(AddFall))]
public virtual int Add(int i, int j)
{
String s = null;
s.ToString();
return i + j;
}
public int AddFall(int i, int j)
{
return 0;
}
}
細化框架
上面明白了了原理,然後直接展示寫好的更復雜的HystrixCommandAttribute,講解代碼。
這是楊中科老師維護的開源項目
github最新地址 https://github.com/yangzhongke/RuPeng.HystrixCore
Nuget地址:https://www.nuget.org/packages/RuPeng.HystrixCore
重試:MaxRetryTimes表示最多重試幾次,如果為0則不重試,RetryIntervalMilliseconds 表示重試間隔的毫秒數;
熔斷:EnableCircuitBreaker是否啟用熔斷,ExceptionsAllowedBeforeBreaking表示熔斷前出現允許錯誤幾次,MillisecondsOfBreak表示熔斷多長時間(毫秒);
超時:TimeOutMilliseconds執行超過多少毫秒則認為超時(0表示不檢測超時)
緩存:CacheTTLMilliseconds 緩存多少毫秒(0 表示不緩存),用“類名+方法名+所有參數值 ToString拼接”做緩存Key(唯一的要求就是參數的類型ToString對於不同對象一定要不一樣)。
用到了緩存組件:Install-Package Microsoft.Extensions.Caching.Memory
using System;
using AspectCore.DynamicProxy;
using System.Threading.Tasks;
using Polly;
namespace RuPeng.HystrixCore
{
[AttributeUsage(AttributeTargets.Method)]
public class HystrixCommandAttribute : AbstractInterceptorAttribute
{
/// <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;
//由於CircuitBreaker要求同一段代碼必須共享同一個Policy對象。
//而方法上標註的Attribute 對於這個方法來講就是唯一的對象,一個方法對應一個方法上標註的Attribute對象。
//一般我們熔斷控制是針對一個方法,一個方法無論是通過幾個 Person 對象調用,無論是誰調用,只要全局出現ExceptionsAllowedBeforeBreaking次錯誤,就會熔斷,這是框架的實現,你如果認為不合理,自己改去。
//我們在Attribute上聲明一個Policy的成員變量,這樣一個方法就對應一個Policy對象。
private Policy 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) //如果出錯等待MaxRetryTimes時間在執行
{
policy = policy.WrapAsync(Policy.Handle<Exception>().WaitAndRetryAsync(MaxRetryTimes, i => TimeSpan.FromMilliseconds(RetryIntervalMilliseconds)));
}
Policy 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);
}
}
}
}
框架不是萬能的,不用過度框架,過度框架帶來的復雜度陡增,從人人喜歡變成人人恐懼。
結合 asp.net core依賴註入
在asp.net core項目中,可以借助於asp.net core的依賴註入,簡化代理類對象的註入,不用再自己調用ProxyGeneratorBuilder 進行代理類對象的註入了。
Install-Package AspectCore.Extensions.DependencyInjection
修改Startup.cs的ConfigureServices方法,把返回值從void改為IServiceProvider
using AspectCore.Extensions.DependencyInjection;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<Person>();
return services.BuildAspectCoreServiceProvider();
}
其 中 services.AddSingleton<Person>(); 表 示 把Person註 入 。
BuildAspectCoreServiceProvider是讓aspectcore接管註入。
在Controller中就可以通過構造函數進行依賴註入了:
public class ValuesController : Controller
{
private Person p;
public ValuesController(Person p)
{
this.p = p;
}
}
通過反射掃描所有Service類,只要類中有標記了CustomInterceptorAttribute的方法都算作服務實現類。為了避免一下子掃描所有類,所以 RegisterServices 還是手動指定從哪個程序集中加載。
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
RegisterServices(this.GetType().Assembly, services); return services.BuildAspectCoreServiceProvider();
}
private static void RegisterServices(Assembly asm, IServiceCollection services)
{
//遍歷程序集中的所有public類型
foreach (Type type in asm.GetExportedTypes())
{
//判斷類中是否有標註了CustomInterceptorAttribute的方法
bool hasCustomInterceptorAttr = type.GetMethods().Any(m => m.GetCustomAttribute(typeof(CustomInterceptorAttribute)) != null);
if (hasCustomInterceptorAttr)
{
services.AddSingleton(type);
}
}
}
註:此文章是我看楊中科老師的.Net Core微服務第二版和.Net Core微服務第二版課件整理出來的
(7)學習筆記 ) ASP.NET CORE微服務 Micro-Service ---- 利用Polly+AOP+依賴註入封裝的降級框架