熔断、降级:

本文基本是官方说明的翻译和总结()

在微服务架构下,我相信大家都应该遇到类似以下问题:

熔断:熔断就是我们常说的“保险丝”,意为当服务出现某些状况时,切断服务,从而防止应用程序不断地常识执行可能会失败的操作造成系统的“雪崩”,或者大量的超时等待导致系统卡死等情况,很多地方也将其成为“过载保护”。

什么是Polly?

yzc579亚洲城官网 1

Polly是一款基于.NET的弹性及瞬间错误处理库,
它允许开发人员以顺畅及线程安全的方式执行重试(Retry),断路器(Circuit),超时(Timeout),隔板隔离(Bulkhead
Isolation)及后背策略(Fallback)。

Polly适用于.NET 4.0, .NET 4.5及.NET Standard 1.1(覆盖.NET Core, Mono,
Xamarin.IOS, Xamarin.Android, UWP, WP 8.1+)。

  1. 某些接口异常,最终造成应用程序池奔溃;
  2. 某些接口不稳定、偶尔超时,数据获取异常;
  3. 某些服务不稳定,调用方连接不上;
  4. 某些服务异常,最终主服务挂掉;

降级:降级的目的就是当某个服务提供者发生故障的时候,向调用方返回一个替代响应或者错误响应。

安装Polly

.NET 4.0版本

Install-Package Polly.Net40Async

.NET 4.5及以上版本, .Net Standard 1.1

Install-Package Polly

当然在实际情况下,可能有时我们只需要确保提供给用户的服务是可用状态,不出现
“Service Unavailable”
这样的画面基本上也可以。至于接口偶尔异常,可能对某些类型的项目来说并不太关键,用户可能通过重新请求、刷新页面就可以解决,当然我们还可以在代码层面做兼容,满满的try/catch、for/while
循环解决重试来保证更高的可靠性。

介绍:

POLLY是一个.NET回弹和瞬态故障处理库,它允许开发人员以流畅和线程安全的方式表达诸如重试、断路器、超时、隔板隔离和回退等策略。github官方解释嘿嘿。

Polly以.NET Standard
1.1(覆盖范围:.NET
Framework 4.5-4.6.1,.NET Core 1.0,Mono,Xamarin,UWP,WP8.1 +)

            .NET Standard
2.0+(覆盖范围:.NET
Framework 4.6.1, .NET Core 2.0+以及后来的Mono,Xamarin和UWP目标)

弹性策略

Polly提供多种弹性策略。

不管这么样,任何异常情况都不是我们期望的,但它却永远存在,”投机取巧“
终将不是谨慎的做法,随着一个项目关联的微服务越来越多,以上问题会表现得越突出,所以选择一个好的故障处理库或框架变得尤为重要。

安装:

首先当然是创建一个控制台项目,然后通过NuGet安装:

Install-Package Polly

yzc579亚洲城官网 2

出现以上界面就说明你已经安装了最新的版本到你的项目;

重试策略

yzc579亚洲城官网,这里介绍一个轻量的故障处理库 Polly ,Polly
是一个.NET弹性和瞬态故障处理库,它允许我们以非常顺畅和线程安全的方式来执行诸如重试、断路器、超时、隔离、缓存、后退等策略,
能为我们在微服务架构提供更稳定的服务。当然,目前的 Service Mesh
显得更高大上,而且更强大,它更偏向从运维层面解决以上问题。不过这些都得看项目的需要来决策。

策略介绍:

polly通过官方介绍我们可以知道有7种恢复策略,先不管会不会,先列出来再说哈哈:

   
重试策略(Retry):许多故障是短暂的,并且在短暂的延迟后可能会自我纠正。允许我们做的是能够自动配置重试机制

 
  断路器(Circuit-breaker):当一个系统严重挣扎时,快速失败优于让用户/呼叫者等待。 说白了就是应该优先直接返回失败,而不是一直让用户等待。保护故障系统免受过载可以帮助恢复。

 
  超时(Timeout):超时策略针对的前置条件是超过一定的等待时间,想要得到成功的结果是不可能的,保证调用者不必等待超时。

 

    隔板隔离(Bulkhead
Isolation):隔板隔离针对的前置条件是当进程出现故障时,多个失败一直在主机中对资源(例如线程/
CPU)一直占用。下游系统故障也可能导致上游失败。这两个风险都将造成严重的后果。都说一粒老鼠子屎搅浑一锅粥,而Polly则将受管制的操作限制在固定的资源池中,免其他资源受其影响。

 

   
缓存(Cache):就是一些请求,会把数据缓存起来,然后在持续一段时间内,直接从缓存中取。

 

   
回退(Fallback):操作仍然会失败,也就是说当发生这样的事情时我们打算做什么。也就是说定义失败返回操作。我们在使用时就是所说的降级。

 

    策略包装(PolicyWrap):不同的故障需要不同的策略 弹性意味着使用组合。

前提

程序会产生许多瞬时故障,但是在一定时间延迟之后,程序会自动纠正故障。

yzc579亚洲城官网 3Polly

使用步骤:

polly一般分为三步进行:

  • 定义条件: 定义你要处理的 错误异常/返回结果

  • 定义处理方式 : 重试,熔断,回退

  • 执行

定义条件: 
  

.Handle<ExceptionType>():限定条件的单个异常Policy;

.Handle<ExceptionType>(ex => ex.Number == 10):具有条件的单个异常类型。

.Handle<HttpRequestException>()
.Or<OperationCanceledException>():多个异常类型,当然他也可以变成具有条件的多个异常类型,类似于单个操作。

定义返回结果的条件:

.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound):用条件处理返回值,处理单个返回值。

.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway):处理多个返回值。

 指定策略(重试):

.Retry():重试一次。

.Retry(3):重试三次,修改数值即可定义自己想使用的次数。

.Retry(3, (exception, retryCount) =>
    {
        // do something 
    }):重试多次,在每次重试都执行一个操作,参数为:当前异常和重试计数。

.Retry(3, (exception, retryCount, context) =>
    {
        // do something 
    }):重试多次,在每次重试都执行一个操作,参数为:当前异常,重试计数和上下文

.RetryForever():永远重试直到成功,同时也也有重试的相同扩展,可以写参数。

.WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  }):等待并重试,就是在每个重试的时候需要等待指定的执行时间,同样有相同扩展,可以在每个重试调用一个操作。

.WaitAndRetryForever(retryAttempt => 
 TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
    ):等待和重试永远(直到成功)

  指定策略(熔断):

.CircuitBreaker(2, TimeSpan.FromMinutes(1)):在指定数量的连续异常之后中断开。这里就不做过多解释了。

 指定策略(降级):回退

.Fallback<UserAvatar>(UserAvatar.Blank):如果执行错误,则提供替代值,就是出现错误,定义一个返回值给他

.Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar()) :定义一个方法给他,该方法代替提供值。

执行: 

.Execute(() => DoSomething()):执行一项方法

执行通过任意上下文数据的操作:

var policy = Policy
    .Handle<SomeExceptionType>()
    .Retry(3, (exception, retryCount, context) =>
    {
        var methodThatRaisedException = context["methodName"];
        Log(exception, methodThatRaisedException);
    });

policy.Execute(
    () => DoSomething(),
    new Dictionary<string, object>() {{ "methodName", "some method" }}
);

实现效果

允许配置自动重试。

Polly
的使用相对比较简单,当然还是得看项目结构。我们的主项目在调用微服务接口时使用了AOP,类似这种情况下,所以调用微服务的接口都是统一入口,所以我们只需要在AOP内加上
Polly 的一些策略,其他代码不用做任何修改,就可以解决一些问题了。

 使用示例:

降级的使用代码:

yzc579亚洲城官网 4yzc579亚洲城官网 5

 #region 降级
        public static void Downgrade()
        {
            //降级处理程序
            ISyncPolicy policy = Policy.Handle<ArgumentException>()
            .Fallback(() =>
            {
                Console.WriteLine("降级给的返回值结果");
            });
            //运行程序
            policy.Execute(() =>
            {
                Console.WriteLine("任务开始");

                throw new ArgumentException("降级任务出错,马上要降级了");

                Console.WriteLine("任务结束");
            });
        }
        #endregion

View Code

运行结果:

yzc579亚洲城官网 6

重试机制的代码:

出错后重复3次。

yzc579亚洲城官网 4yzc579亚洲城官网 8

 #region 重试机制
        public static void Retry()
        {
            //配置重试次数
            ISyncPolicy policy = Policy.Handle<Exception>().Retry(3);

            try
            {
                policy.Execute(() =>
                {
                    Console.WriteLine("任务开始");
                    if (DateTime.Now.Second % 10 != 0)
                    {
                        throw new Exception("任务出错了,开始重试");
                    }
                    Console.WriteLine("任务结束");
                });
            }
            catch (Exception ex)
            {
                Console.WriteLine("异常结果 : " + ex.Message);
            }
        }
        #endregion

View Code

运行结果:

yzc579亚洲城官网 9

 熔断机制代码:

但出现错误连续三次后,等待20秒后进行

yzc579亚洲城官网 4yzc579亚洲城官网 11

 #region 熔断机制
        public static void Fusing()
        {

            Action<Exception, TimeSpan> onBreak = (exception, timespan) =>
            {
                Console.WriteLine("1");
            };
            Action onReset = () =>
            {
                Console.WriteLine("2");
            };
            ISyncPolicy policy = Policy.Handle<Exception>().CircuitBreaker(3, TimeSpan.FromSeconds(20), onBreak, onReset);
            while (true)
            {
                try
                {

                    policy.Execute(() =>
                        {
                            Console.WriteLine("任务开始");

                            throw new Exception("出错了");

                            Console.WriteLine("任务结束");
                        });

                }
                catch (Exception ex)
                {
                    Console.WriteLine("---------------异常结果-------------- : " + ex.Message + "时间:" + DateTime.Now);
                }
                System.Threading.Thread.Sleep(5000);
            }
        }
        #endregion

View Code

运行结果:

yzc579亚洲城官网 12

混合示例(重试+降级)代码:

出错重试三次后第四次进行降级处理:主要是warp方法来实现的,特别说明warp方法是:最外面(在左边)到最里面(右边)的策略。也就是说从右向左执行方法;

yzc579亚洲城官网 4yzc579亚洲城官网 14

 #region 重试+降级
        public static void RetryDowngrade()
        {
            try
            {
                //降级处理程序
                ISyncPolicy policy = Policy.Handle<Exception>()
                .Fallback(() =>
                {
                    Console.WriteLine("降级成功");
                });
                //配置重试次数
                ISyncPolicy policy2 = Policy.Handle<Exception>().Retry(3, (exception, retryCount, context) =>
                             {
                                 Console.WriteLine(retryCount);

                             });
                //合并
                ISyncPolicy mainPolicy = Policy.Wrap(policy, policy2);
                mainPolicy.Execute(() =>
                {
                    Console.WriteLine("任务开始");

                    throw new Exception("出错了");

                    Console.WriteLine("任务结束");
                });
            }
            catch (Exception ex)
            {

                Console.WriteLine("异常结果 : " + ex.Message);
            }
        }
        #endregion

View Code

运行结果:

yzc579亚洲城官网 15

 源码下载:PollyConsole.rar

断路器策略

安装

Install-Package Polly

系列目录

微服务系列文章主要介绍微服务所使用到的一些技术和一些技术示例:

  • 微服务——微服务的介绍和目录
  • 微服务——【Consul】服务发现在windows下简单使用(一)
  • 微服务——【polly】微服务故障处理库(二)
  • 微服务——动态代理AspectCore的使用(三) 
  • 微服务——网关Ocelot+Consul实现集群轮询(四)

前提

当系统发生严重故障时,快速响应请求失败比让用户等待要好。
避免故障系统过载有助于恢复系统。

使用步骤说明

  1. 定义策略
  2. 执行方法

我们项目中的 Polly 部分代码如下:

public void Intercept(IInvocation invocation){ // some code try { // 创建一个策略,如果 invocation.Proceed 的执行出现 Grpc.Core.RpcException 异常,并且 StatusCode == Grpc.Core.StatusCode.Unavailable,则重试一次 var policy = Policy .Handle<Grpc.Core.RpcException>(t => t.Status.StatusCode == Grpc.Core.StatusCode.Unavailable) .Retry(); // 默认一次 // 将策略应用到 invocation.Proceed 方法上 policy.Execute(invocation.Proceed); } catch (Exception ex) { // some code Console.WriteLine($"{ ex.Message},{ex.StackTrace}"); }}

实现效果

当系统错误超过预配置的数量,系统将断路一段时间。

策略条件定义

策略的执行需要依赖于条件,Polly 支持对异常与结果进行策略条件定义。

异常

// 指定某个异常Policy .Handle<SomeExceptionType>();// 指定某个异常条件Policy .Handle<SomeExceptionType>(ex => ex.xxx == "xxx")// 指定多个异常Policy .Handle<SomeExceptionType1>() .Or<SomeExceptionType2>()// 指定多个可能异常条件Policy .Handle<SomeExceptionType1>(ex => ex.xxx1 == "xxx") .Or<SomeExceptionType2>(ex => ex.xxx2 == "xxx")

返回结果

// 指定某个结果Policy .HandleResult<ResponseMessage>(r => r.xxx == "xxx")// 指定多个可能的结果Policy .HandleResult<ResponseMessage>(r => r.xxx1 == "xxx") .OrResult<ResponseMessage>(r => r.xxx2 == "xxx")

超时策略

重试策略

// 指定异常下重试一次Policy .Handle<SomeExceptionType>() .Retry();// 指定异常下重试3次Policy .Handle<SomeExceptionType>() .Retry;// 指定异常下无限重试Policy .Handle<SomeExceptionType>() .RetryForever();// 每次重试之间等待指定的时间间隔Policy .Handle<SomeExceptionType>() .WaitAndRetry(new[] { TimeSpan.FromSeconds, TimeSpan.FromSeconds, TimeSpan.FromSeconds;

Retry 可以指定一个要执行的 Action。Action 参数:exception
当前异常信息,retryCount 当前执行第几次,context 当前执行上下文信息。

测试代码:

private static int times = 0;public static void TestPolicy(){ var policy = Policy .Handle<Exception>() .Retry(3, (exception, retryCount, context) => // 出异常会执行以下代码 { Console.WriteLine($"exception:{ exception.Message}, retryCount:{retryCount}, id:{context["id"]}, name:{context["name"]}"); }); try { // 通过 new Context 传递上下文信息 var result = policy.Execute(Test, new Context("data", new Dictionary<string, object>() { { "id", "1" }, { "name", "beck" } })); Console.WriteLine($"result:{result}"); } catch (Exception ex) { Console.WriteLine(ex.Message); }}private static string Test(){ // 每执行一次加1 times++; // 前2次都抛异常 if (times < 3) { throw new Exception("exception message"); } return "success";}

测试结果:

yzc579亚洲城官网 16retry

前提

超出一定时间的等待,想要得到正确的结果是不太可能的。

参考链接

  • Polly
  • Polly Project
  • PollySamples

实现效果

保证调用者不需要等待超时。

隔板隔离

前提

当进程出现故障,多个失败的请求很容易占满服务器资源(线程/CPU)。
一个处于故障状态的下游系统,也会导致其上游系统故障。

实现效果

将严格管控故障进程,使其使用固定大小的资源池,隔离他们对其他进程的潜在影响

缓存策略

前提

一定比例的请求可能是相似的。

实现效果

从缓存中提供已知的响应。
当第一次读取的时候,将响应自动缓存起来。

后备策略

前提

当故障依然存在的时候,你打算做什么。

实现效果

当程序依然发生故障时,执行指定操作。

包装策略

前提

不同的故障需要不同的策略。包装策略即组合策略。

实现效果

允许灵活的将以上任意几种策略组合在一起。

如何使用Polly进行故障/异常处理?

Polly处理故障/异常有以下几个步骤。

  1. 指定处理的异常/故障类型
  2. [可选] 指定处理的异常返回值
  3. 指定处理策略
  4. 执行策略

指定处理异常/故障的类型

Polly使用Policy类的泛型方法Handle指定Polly需要处理异常/故障的类型。

指定单个异常类型

Policy.Handle<DivideByZeroException>()

指定带条件的异常类型

Policy.Handle<SqlException>(ex => ex.Number == 1205)

Polly也支持指定多种异常/故障类型, 这里需要使用Or方法

Policy.Handle<DivideByZeroException>().Or<ArgumentException>()

指定多个带条件的异常类型

Policy
   .Handle<SqlException>(ex =ex.Number == 1205)
   .Or<ArgumentException>(ex =ex.ParamName == "example")

Polly也支持指定内部异常

Policy
    .HandleInner<HttpResponseException>()
    .OrInner<OperationCanceledException>(ex => ex.CancellationToken == myToken)

指定处理的异常返回值

Polly除了支持处理异常/故障类型,还支持处理异常返回值。所谓的处理异常结果,就是当Polly监控的方法,返回某些特定结果时,
Polly会触发异常/故障处理策略。

Polly使用Policy类的泛型方法HandleResult制定Polly需要处理的异常结果.

指定触发异常/故障处理策略的返回值

例如:当某个方法的返回值类型是HttpResposneMessage,
并且返回值的StatusCode是NotFound时,触发异常/故障处理策略。

Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)

指定多个返回值

Policy
    .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
    .OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway)

同时指定异常类型和返回值

HttpStatusCode[] httpStatusCodesWorthRetrying = {
    HttpStatusCode.RequestTimeout, // 408
    HttpStatusCode.InternalServerError, // 500
    HttpStatusCode.BadGateway, // 502
    HttpStatusCode.ServiceUnavailable, // 503
    HttpStatusCode.GatewayTimeout // 504
}; 
HttpResponseMessage result = Policy
    .Handle<HttpResponseException>()
    .OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))

指定异常处理策略

重试策略

重试一次

Policy
    .Handle<DivideByZeroException>()
    .Retry()

重试多次

Policy
    .Handle<DivideByZeroException>()
    .Retry(3)

重试多次,每次重试触发一个行为

Policy
    .Handle<DivideByZeroException>()
    .Retry(3, (exception, retryCount) =>
    {
        // do something 
    });

永久重试(直到成功)

永久重试

Policy
    .Handle<DivideByZeroException>()
    .RetryForever()

永久重试,每次重试触发一个行为

Policy
    .Handle<DivideByZeroException>()
    .RetryForever(exception =>
    {
            // do something       
    });

等待并重试

指定每个重试的间隔时间

Policy
    .Handle<DivideByZeroException>()
    .WaitAndRetry(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(3)
    });

在这个例子如果第一次出现异常,会在1秒后重试,如果依然出现异常,会在再次出现异常后2秒继续重试,以此类推,下次异常后3秒继续重试

每次重试,触发一个行为

Policy
    .Handle<DivideByZeroException>()
    .WaitAndRetry(new[]
    {
      TimeSpan.FromSeconds(1),
      TimeSpan.FromSeconds(2),
      TimeSpan.FromSeconds(3)
    }, (exception, timeSpan) => {
      // do something    
    }); 

断路器策略

在发生指定次数的异常/故障之后,断开回路

Policy
    .Handle<DivideByZeroException>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1));

发生2次异常之后,断开回路1分钟

Action<Exception, TimeSpan> onBreak = (exception, timespan) => { ... };
Action onReset = () => { ... };
CircuitBreakerPolicy breaker = Policy
    .Handle<DivideByZeroException>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

发生2次异常之后,断开回路1分钟,
在触发断路时触发onBreak方法,当重置断路器时,触发onReset方法

后备策略

Policy
    .Handle<Whatever>()
    .Fallback<UserAvatar>(UserAvatar.Blank)

当程序触发异常/故障后,返回一个备用值

Policy
    .Handle<Whatever>()
    .Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar())

当程序触发异常/故障后,使用一个方法返回一个备用值

Policy
   .Handle<Whatever>()
   .Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) => 
    {
        // do something
    });

当程序触发异常/故障后,返回一个备用值,并触发一个方法

Policy
   .Handle<Whatever>()
   .Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) => 
    {
        // do something
    });

执行策略

Polly将监控DoSomething方法,如果发生DivideByZeroException异常,就使用重试策略

var policy = Policy
              .Handle<DivideByZeroException>()
              .Retry();

policy.Execute(() => DoSomething());

向Polly上下文中传递任意值

var policy = Policy
    .Handle<DivideByZeroException>()
    .Retry(3, (exception, retryCount, context) =>
    {
        var methodThatRaisedException = context["methodName"];
        Log(exception, methodThatRaisedException);
});

policy.Execute(
    () => DoSomething(),
    new Dictionary<string, object>() {{ "methodName", "some method" }}
);

Author

发表评论

电子邮件地址不会被公开。 必填项已用*标注