1. 程式人生 > >(7)學習筆記 ) ASP.NET CORE微服務 Micro-Service ---- 利用Polly+AOP+依賴註入封裝的降級框架

(7)學習筆記 ) ASP.NET CORE微服務 Micro-Service ---- 利用Polly+AOP+依賴註入封裝的降級框架

tostring methods summary bstr 判斷 KS foreach public tde

創建簡單的熔斷降級框架

要達到的目標是: 參與降級的方法參數要一樣,當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+依賴註入封裝的降級框架