net6 polly 故障处理组件简单介绍+ 实战封装

1 polly 主要是故障处理组件

   Polly是一个开源的弹性跟瞬态故障处理类库。它可以在你的程序出现故障,超时,或者返回值达成某种条件的时候进行多种策略处理,比如重试、超时、降级、熔断等等。

源码地址:https://github.com/App-vNext/Polly

1.1.超时策略

1.2重试策略

1.3降级策略

1.4熔断策略

1.5策略包裹(多重策略组装)

 

创建一个控制台 一个webapi  控制调用api 

 在控制台引入nuget 包

Install-Package Polly

webapi 项目创建几个接口 如下
// 定义超时调用的APi
app.MapGet("/api/polly/timeout", () =>
{
    Thread.Sleep(6000);
    return "Polly Timeout";
});
// 定义500结果的APi
app.MapGet("/api/polly/500", (HttpContext context) =>
{
    context.Response.StatusCode = 500;
    return "fail";
});
// 定义/api/user
app.MapGet("/api/user/1", () =>
{
    var user = new Entiyt_memlst
    {
        u_id=10000,
         u_nme = "Jason Chen"
    };
    return user;
});

  

控制台代码如下
#region 超时策略  设定5秒超时  api 需要6秒返回结果 所以回执行超时
//var memberJson = await Policy.TimeoutAsync(5, TimeoutStrategy.Pessimistic, (t, s, y) =>
//{
//    Console.WriteLine("超时了~");
//    return Task.CompletedTask;
//}).ExecuteAsync(async () =>
//{
//    // 业务逻辑
//    using var httpClient = new HttpClient();
//    httpClient.BaseAddress = new Uri($"http://localhost:5050");
//    var memberResult = await httpClient.GetAsync("/api/polly/timeout");
//    memberResult.EnsureSuccessStatusCode();
//    var json = await memberResult.Content.ReadAsStringAsync();
//    Console.WriteLine(json);
//    return json;
//});
#endregion

#region Polly 重试策略
//  重试有2种方式 一种是异常重试 一种是根据状态码重试
//  1当发生 HttpRequestException 的时候触发 RetryAsync 重试,并且最多重试3次。 
//var memberJson = await Policy.Handle<HttpRequestException>().RetryAsync(3).ExecuteAsync(async () =>
//{
//    Console.WriteLine("重试中.....");
//    using var httpClient = new HttpClient();
//    httpClient.BaseAddress = new Uri($"http://localhost:5050");
//    var memberResult = await httpClient.GetAsync("/member/1001");
//    memberResult.EnsureSuccessStatusCode();
//    var json = await memberResult.Content.ReadAsStringAsync();
//    return json;
//});

//2使用 Polly 在出现当请求结果为 http status_code 500 的时候进行3次重试。
//var memberResult = await Policy.HandleResult<HttpResponseMessage>
//    (x => (int)x.StatusCode == 500).RetryAsync(3).ExecuteAsync(async () =>
//{
//    Thread.Sleep(1000);
//    Console.WriteLine("响应状态码重试中.....");
//    using var httpClient = new HttpClient();
//    httpClient.BaseAddress = new Uri($"http://localhost:5050");
//    var memberResult = await httpClient.GetAsync("/api/polly/500");
//    return memberResult;
//});
#endregion

#region 服务降级
//首先我们使用 Policy 的 FallbackAsync("FALLBACK") 方法设置降级的返回值。当我们服务需要降级的时候会返回 "FALLBACK" 的固定值。
//同时使用 WrapAsync 方法把重试策略包裹起来。这样我们就可以达到当服务调用失败的时候重试3次,如果重试依然失败那么返回值降级为固定的 "FALLBACK" 值。
//var fallback = Policy<string>.Handle<HttpRequestException>().Or<Exception>().FallbackAsync("FALLBACK", (x) =>
//{
//    Console.WriteLine($"进行了服务降级 -- {x.Exception.Message}");
//    return Task.CompletedTask;
//}).WrapAsync(Policy.Handle<HttpRequestException>().RetryAsync(3));

//var memberJson = await fallback.ExecuteAsync(async () =>
//{
//    using var httpClient = new HttpClient();
//    httpClient.BaseAddress = new Uri($"http://localhost:5050");
//    var result = await httpClient.GetAsync("/api/user/" + 1);
//    result.EnsureSuccessStatusCode();
//    var json = await result.Content.ReadAsStringAsync();
//    return json;

//});
//Console.WriteLine(memberJson);//  当我们服务需要降级的时候会返回 "FALLBACK" 的固定值
//if (memberJson != "FALLBACK")
//{
//    var member = JsonConvert.DeserializeObject<Entiyt_memlst>(memberJson);
//    Console.WriteLine($"{member!.u_id}---{member.u_nme}");
//}
#endregion

//#region 服务熔断
////定义熔断策略
//var circuitBreaker = Policy.Handle<Exception>().CircuitBreakerAsync(
//   exceptionsAllowedBeforeBreaking: 2, // 出现几次异常就熔断
//   durationOfBreak: TimeSpan.FromSeconds(10), // 熔断10秒
//   onBreak: (ex, ts) =>
//   {
//       Console.WriteLine("circuitBreaker onBreak ."); // 打开断路器
//   },
//   onReset: () =>
//   {
//       Console.WriteLine("circuitBreaker onReset "); // 关闭断路器
//   },
//   onHalfOpen: () =>
//   {
//       Console.WriteLine("circuitBreaker onHalfOpen"); // 半开 放部分流量试探服务是否正常
//   }
//);

//// 定义重试策略
//var retry = Policy.Handle<HttpRequestException>().RetryAsync(3);
//// 定义降级策略
//var fallbackPolicy = Policy<string>.Handle<HttpRequestException>().Or<BrokenCircuitException>()
//    .FallbackAsync("FALLBACK", (x) =>
//    {
//        Console.WriteLine($"进行了服务降级 -- {x.Exception.Message}");
//        return Task.CompletedTask;
//    })
//    .WrapAsync(circuitBreaker.WrapAsync(retry));///  这里这是嵌套策略  折行顺序是先重试  在熔断 最后在降级   
 
//string memberJsonResult = "";

//do
//{
//    memberJsonResult = await fallbackPolicy.ExecuteAsync(async () =>
//    {
//        using var httpClient = new HttpClient();
//        httpClient.BaseAddress = new Uri($"http://localhost:5050");
//        var result = await httpClient.GetAsync("/api/user/" + 1);
//        result.EnsureSuccessStatusCode();
//        var json = await result.Content.ReadAsStringAsync();
//        return json;
//    });
//    Thread.Sleep(1000);
//} while (memberJsonResult == "FALLBACK");

//if (memberJsonResult != "FALLBACK")
//{
//    var member = JsonConvert.DeserializeObject<Entiyt_memlst>(memberJsonResult);
//    Console.WriteLine($"{member!.u_id}---{member.u_nme}");
//}
//#endregion

 

 包裹策略 效果展示如下:

 

 2 实际开发中我们不可能一个方法写一次 polly 策略。所以要aop 一下。 这里用了autofac 里面的aop

   需要新建一进程用来调用上面的webapi   新建的类有IUserService  、UserService  (实现接口) CustomPollyPolicyInterceptor (polly 切面逻辑)

定义 IUserService  

   [Intercept(typeof(CustomPollyPolicyInterceptor))]//表示要polly生效
    public interface IUserService
    {
        [PollyPolicyConfig(FallBackMethod = "UserServiceFallback",//UserServiceFallback 方法名称
            IsEnableCircuitBreaker = true,
            ExceptionsAllowedBeforeBreaking = 3,
            MillisecondsOfBreak = 1000 * 5,
            CacheTTLMilliseconds = 1000 * 20)]
        User AOPGetById(int id);

        Task<User> GetById(int id);
    }

    public record User(int Id, string Name, string Account, string Password);

 

 

 

UserService 类如下

        /// <summary>
        /// 【AOP方式定义】根据用户ID查询用户信息
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public User AOPGetById(int id)
        {
            string url = $"http://localhost:5050/api/user/1";
         //   string realUrl = abstractConsulDispatcher.MapAddress(url);
            string content = httpAPIInvoker.HttpInvoke(url);
            var user = JsonConvert.DeserializeObject<User>(content)!;
            return  user;
        }

 

autofac  CustomPollyPolicyInterceptor切面逻辑逻辑如下:

    [AttributeUsage(AttributeTargets.Method)]
    public class CustomPollyPolicyInterceptor : Attribute,IInterceptor
    {
        private static ConcurrentDictionary<MethodInfo, AsyncPolicy> policies
        = new ConcurrentDictionary<MethodInfo, AsyncPolicy>();
        private static readonly IMemoryCache memoryCache
          = new MemoryCache(new MemoryCacheOptions());

        public void Intercept(IInvocation invocation)
        {
            if (!invocation.Method.IsDefined(typeof(PollyPolicyConfigAttribute), true))
            {
                // 直接调用方法本身
                invocation.Proceed();
            }
            else
            {
                PollyPolicyConfigAttribute pollyPolicyConfigAttribute = invocation.Method.GetCustomAttribute<PollyPolicyConfigAttribute>()!;
                //一个PollyPolicyAttribute中保持一个policy对象即可
                //其实主要是CircuitBreaker要求对于同一段代码要共享一个policy对象
                //根据反射原理,同一个方法的MethodInfo是同一个对象,但是对象上取出来的PollyPolicyAttribute
                //每次获取的都是不同的对象,因此以MethodInfo为Key保存到policies中,确保一个方法对应一个policy实例
                policies.TryGetValue(invocation.Method, out AsyncPolicy? policy);
                //把本地调用的AspectContext传递给Polly,主要给FallbackAsync中使用
                // 创建Polly上下文对象(字典)
                Context pollyCtx = new();
                pollyCtx["invocation"] = invocation;

                lock (policies)//因为Invoke可能是并发调用,因此要确保policies赋值的线程安全
                {
                    if (policy == null)
                    {
                        policy = Policy.NoOpAsync();//创建一个空的Policy
                        if (pollyPolicyConfigAttribute.IsEnableCircuitBreaker)
                        {
                            policy = policy.WrapAsync(Policy.Handle<Exception>()
                                .CircuitBreakerAsync(pollyPolicyConfigAttribute.ExceptionsAllowedBeforeBreaking,
                                TimeSpan.FromMilliseconds(pollyPolicyConfigAttribute.MillisecondsOfBreak),
                                onBreak: (ex, ts) =>
                                {
                                    Console.WriteLine($"熔断器打开 熔断{pollyPolicyConfigAttribute.MillisecondsOfBreak / 1000}s.");
                                },
                                onReset: () =>
                                {
                                    Console.WriteLine("熔断器关闭,流量正常通行");
                                },
                                onHalfOpen: () =>
                                {
                                    Console.WriteLine("熔断时间到,熔断器半开,放开部分流量进入");
                                }));
                        }
                        if (pollyPolicyConfigAttribute.TimeOutMilliseconds > 0)
                        {
                            policy = policy.WrapAsync(Policy.TimeoutAsync(() =>
                                TimeSpan.FromMilliseconds(pollyPolicyConfigAttribute.TimeOutMilliseconds),
                                Polly.Timeout.TimeoutStrategy.Pessimistic));
                        }
                        if (pollyPolicyConfigAttribute.MaxRetryTimes > 0)
                        {
                            policy = policy.WrapAsync(Policy.Handle<Exception>()
                                .WaitAndRetryAsync(pollyPolicyConfigAttribute.MaxRetryTimes, i =>
                                TimeSpan.FromMilliseconds(pollyPolicyConfigAttribute.RetryIntervalMilliseconds)));
                        }
                        // 定义降级测试 可以不要重试 可以不要限流 可以不要熔断 但需要一个降级策略
                        var policyFallBack = Policy.Handle<Exception>().FallbackAsync((fallbackContent, token) =>
                        {
                            // 必须从Polly的Context种获取IInvocation对象
                            IInvocation iv = (IInvocation)fallbackContent["invocation"];
                            var fallBackMethod = iv.TargetType.GetMethod(pollyPolicyConfigAttribute.FallBackMethod!);
                            var fallBackResult = fallBackMethod!.Invoke(iv.InvocationTarget, iv.Arguments);
                            iv.ReturnValue = fallBackResult;
                            return Task.CompletedTask;
                        }, (ex, t) =>
                        {
                            Console.WriteLine("====================>触发服务降级");
                            return Task.CompletedTask;
                        });

                        policy = policyFallBack.WrapAsync(policy);
                        //放入到缓存
                        policies.TryAdd(invocation.Method, policy);
                    }
                }

                // 是否启用缓存
                if (pollyPolicyConfigAttribute.CacheTTLMilliseconds > 0)
                {
                    //用类名+方法名+参数的下划线连接起来作为缓存key
                    string cacheKey = "PollyMethodCacheManager_Key_" + invocation.Method.DeclaringType
                                                                       + "." + invocation.Method + string.Join("_", invocation.Arguments);
                    //尝试去缓存中获取。如果找到了,则直接用缓存中的值做返回值
                    if (memoryCache.TryGetValue(cacheKey, out var cacheValue))
                    {
                        invocation.ReturnValue = cacheValue;
                    }
                    else
                    {
                        //如果缓存中没有,则执行实际被拦截的方法
                        Task task = policy.ExecuteAsync(
                            async (context) => {
                                invocation.Proceed();
                                await Task.CompletedTask;
                            },
                            pollyCtx
                        );
                        task.Wait();

                        //存入缓存中
                        using var cacheEntry = memoryCache.CreateEntry(cacheKey);
                        {
                            cacheEntry.Value = invocation.ReturnValue;
                            cacheEntry.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMilliseconds(pollyPolicyConfigAttribute.CacheTTLMilliseconds);
                        }
                    }
                }
                else//如果没有启用缓存,就直接执行业务方法
                {
                    Task task = policy.ExecuteAsync(
                            async (context) => {
                                invocation.Proceed();
                                await Task.CompletedTask;
                            },
                            pollyCtx
                        );
                    task.Wait();
                }
            }
        }
    }

在autofac 里面注册 单例  这样polly 才能触发 熔断机制

                buider.RegisterType<UserService>().As<IUserService>().SingleInstance().EnableInterfaceInterceptors();
                buider.RegisterType<CustomPollyPolicyInterceptor>();

 

posted @ 2022-02-11 17:25  非著名架构师  阅读(704)  评论(0编辑  收藏  举报