ASP.NET Core 微服务初探[2]:熔断降级之Polly
当我们从单体架构迁移到微服务模式时,其中一个比较大的变化就是模块(业务,服务等)间的调用方式。在以前,一个业务流程的执行在一个进程中就完成了,但是在微服务模式下可能会分散到2到10个,甚至更多的机器(微服务)上,这必然就要使用网络进行通信。而网络本身就是不可靠的,并随着每个服务都根据自身的情况进行的动态扩容,以及机器漂移等等。可以说,在微服务中,网络连接缓慢,资源繁忙,暂时不可用,服务脱机等异常情况已然变成了一种常态。因此我们必须要有一种机制来保证服务整体的稳定性,而本文要介绍的熔断降级就是一种很好的应对方案。
服务熔断
在介绍熔断之前,我们先来谈谈微服务中的雪崩效应。在微服务中,服务A调用服务B,服务B可能会调用服务C,服务C又可能调用服务D等等,这种情况非常常见。如果服务D出现不可用或响应时间过长,就会导致服务C原来越多的线程处于网络调用等待状态,进而影响到服务B,再到服务A等,最后会耗尽整个系统的资源,导致整体的崩溃,这就是微服务中的“雪崩效应”。
而熔断机制就是应对雪崩效应的一种链路保护机制。其实,对于熔断这个词我们并不陌生,在日常生活中经常会接触到,比如:家用电力过载保护器,一旦电压过高(发生漏电等),就会立即断电,有些还会自动重试,以便在电压正常时恢复供电。再比如:股票交易中,如果股票指数过高,也会采用熔断机制,暂停股票的交易。同样,在微服务中,熔断机制就是对超时的服务进行短路,直接返回错误的响应信息,而不再浪费时间去等待不可用的服务,防止故障扩展到整个系统,并在检测到该服务正常时恢复调用链路。
服务降级
当我们谈到服务熔断时,经常会提到服务降级,它可以看成是熔断器的一部分,因为在熔断器框架中,通常也会包含服务降级功能。
降级的目的是当某个服务提供者发生故障的时候,向调用方返回一个错误响应或者替代响应。从整体负荷来考虑,某个服务熔断后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,这样,虽然服务水平下降,但总比直接挂掉的要好。比如:调用联通接口服务器发送短信失败之后,改用移动短信服务器发送,如果移动短信服务器也失败,则改用电信短信服务器,如果还失败,则返回“失败”响应;再比如:在从推荐商品服务器加载数据的时候,如果失败,则改用从缓存中加载,如果缓存也加载失败,则返回一些本地替代数据。
在某些情况下,我们也会采取主动降级的机制,比如双十一活动等,由于资源的有限,我们也可以把少部分不重要的服务进行降级,以保证重要服务的稳定,待度过难关,再重新开启。
Polly基本使用
在.Net Core中有一个被.Net基金会认可的库Polly,它一种弹性和瞬态故障处理库,可以用来简化对服务熔断降级的处理。主要包含以下功能:重试(Retry),断路器(Circuit-breaker),超时检测(Timeout),舱壁隔离(Bulkhead Isolation), 缓存(Cache),回退(FallBack)。
该项目作者现已成为.NET基金会一员,一直在不停的迭代和更新,项目地址: https://github.com/App-vNext/Polly。
策略
在Polly中,有一个重要的概念:Policy,策略有“故障定义”和“故障恢复”两部分组成。故障是指异常、非预期的返回值等情况,而动作则包括重试(Retry)、熔断(Circuit-Breaker)、Fallback(降级)等。
故障定义
故障也可以说是触发条件,它使用Handle<T>
来定义,表示在什么情况下,才对其进行处理(熔断,降级,重试等)。
一个简单的异常故障定义如下:
Policy.Handle<HttpRequestException>()
如上,表示当我们的代码触发HttpRequestException
异常时,才进行处理。
我们也可以对异常的信息进行过滤:
Policy.Handle<SqlException>(ex => ex.Number == 1205)
如上,只有触发SqlException
异常,并且其异常号为1205
的时候才进行处理。
如果我们希望同时处理多种异常,可以使用Or<T>
来实现:
Policy.Handle<HttpRequestException>().Or<OperationCanceledException>()
Policy.Handle<SqlException>(ex => ex.Number == 1205).Or<ArgumentException>(ex => ex.ParamName == "example")
除此之外,我们还可以根据返回结果进行故障定义:
Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)
如上,当返回值为HttpResponseMessage
,并且其StatusCode
为NotFound
时,才对其进行处理。更多用法参考:usage--fault-handling-policies。
故障恢复
当定义了故障后,要考虑便是如何对故障进行恢复了,Polly中常用的有以下几种恢复策略:
重试(Retry)策略
重试就是指Polly在调用失败时捕获我们指定的异常,并重新发起调用,如果重试成功,那么对于调用者来说,就像没有发生过异常一样。在网络调用中经常出现瞬时故障,那么重试机制就非常重要。
一个简单的重试策略定义如下:
// 当发生HttpRequestException异常时,重试3次
var retryPolicy = Policy.Handle<HttpRequestException>().Retry(3);
有些情况下,如果故障恢复的太慢,我们重试的过快是没有任何任何意义的,这时可以指定重试的时间间隔:
Policy.Handle<HttpRequestException>().WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1)));
如上,重试五次,并且重试时间指数级增加。
超时(Timeout)策略
超时是我们比较常见的,比如HttpClient
就可以设置超时时间,如果在指定的时间内还没有返回,就触发一个TimeoutException
异常,而Polly的超时机制与其类似,只不过超时时触发的是一个TimeoutRejectedException
异常。
// 如果30秒种内没有执行完成,就触发`TimeoutRejectedException`异常
Policy.TimeoutAsync(30);
// 设置超时回调
Policy.TimeoutAsync(30, onTimeout: (context, timespan, task) =>
{
// do something
});
由于超时策略本身就是抛出一个超时异常,所以不需要设置触发条件。
回退(FallBack)策略
回退也称服务降级,用来指定发生故障时的备用方案。
Policy<string>.Handle<HttpRequestException>().FallbackAsync("substitute data", (exception, context) =>
{
// do something
});
如上,如果触发HttpRequestException
异常时,就返回固定的substitute data
。
熔断(Circuit-breaker)策略
断路器用于在服务多次不可用时,快速响应失败,保护系统故障免受过载。
Policy.Handle<HttpRequestException>().Or<TimeoutException>()
.CircuitBreakerAsync(
// 熔断前允许出现几次错误
exceptionsAllowedBeforeBreaking: 3,
// 熔断时间
durationOfBreak: TimeSpan.FromSeconds(100),
// 熔断时触发
onBreak: (ex, breakDelay) =>
{
// do something
},
// 熔断恢复时触发
onReset: () =>
{
// do something
},
// 在熔断时间到了之后触发
onHalfOpen: () =>
{
// do something
}
);
如上,如果我们的业务代码连续失败3次,就触发熔断(onBreak),就不会再调用我们的业务代码,而是直接抛出BrokenCircuitException
异常。当熔断时间(100s)过后,切换为HalfOpen
状态,触发onHalfOpen
事件,此时会再调用一次我们的业务代码,如果调用成功,则触发onReset
事件,并解除熔断,恢复初始状态,否则立即切回熔断状态。
更多策略的用法查看:usage--general-resilience-policie。
执行
在上面的示例中,我们熟悉了各种策略的定义,那么接下来就是执行它。也就是使用Polly
包裹我们的业务代码,Polly
会拦截业务代码中的故障,并根据指定的策略进行恢复。
最简单的策略执行方式如下:
var policy = /*策略定义*/;
var res = await policy.ExecuteAsync(/*业务代码*/);
如果需要同时指定多个策略,可以使用Policy.Wrap
来完成:
Policy.Wrap(retry, breaker, timeout).ExecuteAsync(/*业务代码*/);
其实Warp
本质就是多个策略的嵌套执行,使用如下写法效果是一样的:
fallback.Execute(() => waitAndRetry.Execute(() => breaker.Execute(action)));
关于Polly
更详细的用法可以查看Polly Github上的https://github.com/App-vNext/Polly/wiki,本文就不再过多介绍。
Polly熔断降级实战
场景:轮询调用服务A和服务B,单次调用时间不得超过1s,调用失败时自动切换到另外一个服务重试一次,如果都失败,进行优雅的降级,返回模拟数据,并在2个服务都多次失败后进行熔断。
首先创建一个ASP.NET Core Console程序,命名为PollyDemo。
然后引入Polly的官方Nuge包:
dotnet add package Polly
在我们首先定义一个超时策略:
var timeoutPolicy = Policy.TimeoutAsync(1, (context, timespan, task) =>
{
Console.WriteLine("It's Timeout, throw TimeoutRejectedException.");
return Task.CompletedTask;
});
可以根据实际情况来设置超时时间,我这里为了方便测试,就设置为1s。
然后定义重试策略:
var retryPolicy = Policy.Handle<HttpRequestException>().Or<TimeoutException>().Or<TimeoutRejectedException>()
.WaitAndRetryAsync(
retryCount: 2,
sleepDurationProvider: retryAttempt =>
{
var waitSeconds = TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1));
Console.WriteLine(DateTime.Now.ToString() + "-Retry:[" + retryAttempt + "], wait " + waitSeconds + "s!");
return waitSeconds;
});
再定义一个熔断策略:
var circuitBreakerPolicy = Policy.Handle<HttpRequestException>().Or<TimeoutException>().Or<TimeoutRejectedException>()
.CircuitBreakerAsync(
// 熔断前允许出现几次错误
exceptionsAllowedBeforeBreaking: 2,
// 熔断时间
durationOfBreak: TimeSpan.FromSeconds(3),
// 熔断时触发
onBreak: (ex, breakDelay) =>
{
Console.WriteLine(DateTime.Now.ToString() + "Breaker->Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms! Exception: ", ex.Message);
},
// 熔断恢复时触发
onReset: () =>
{
Console.WriteLine(DateTime.Now.ToString() + "Breaker->Call ok! Closed the circuit again.");
},
// 在熔断时间到了之后触发
onHalfOpen: () =>
{
Console.WriteLine(DateTime.Now.ToString() + "Breaker->Half-open, next call is a trial.");
}
);
如上,连续错误2次就熔断3秒。
最后,再定义一个回退策略:
var fallbackPolicy = Policy<string>.Handle<Exception>()
.FallbackAsync(
fallbackValue: "substitute data",
onFallbackAsync: (exception, context) =>
{
Console.WriteLine("It's Fallback, Exception->" + exception.Exception.Message + ", return substitute data.");
return Task.CompletedTask;
});
我们的业务代码如下:
private List<string> services = new List<string> { "localhost:5001", "localhost:5002" };
private int serviceIndex = 0;
private HttpClient client = new HttpClient();
private Task<string> HttpInvokeAsync()
{
if (serviceIndex >= services.Count)
{
serviceIndex = 0;
}
var service = services[serviceIndex++];
Console.WriteLine(DateTime.Now.ToString() + "-Begin Http Invoke->" + service);
return client.GetStringAsync("http://" + service + "/api/values");
}
这里方便测试,直接写死了两个服务,对其轮询调用,在生产环境中可以参考上一篇《服务发现之Consul》来实现服务发现和负载均衡。
现在,我们组合这些策略来调用我们的业务代码:
for (int i = 0; i < 100; i++)
{
Console.WriteLine(DateTime.Now.ToString() + "-Run[" + i + "]-----------------------------");
var res = await fallbackPolicy.WrapAsync(Policy.WrapAsync(circuitBreakerPolicy, retryPolicy, timeoutPolicy)).ExecuteAsync(HttpInvokeAsync);
Console.WriteLine(DateTime.Now.ToString() + "-Run[" + i + "]->Response" + ": Ok->" + res);
await Task.Delay(1000);
Console.WriteLine("--------------------------------------------------------------------------------------------------------------------");
}
如上,循环执行100次,策略的执行是非常简单的,唯一需要注意的就是调用的顺序:如上是依次从右到左进行调用,首先是进行超时的判断,一旦超时就触发TimeoutRejectedException
异常,然后就进入到了重试策略中,如果重试了一次就成功了,那就直接返回,不再触发其他策略,否则就进入到熔断策略中:
如上图,服务A(localhost:5001)和服务B(localhost:5001),都没有启动,所以会一直调用失败,最后熔断器开启,并最终被降级策略拦截,返回substitute data
。
现在我们启动服务A,可以看到服务会自动恢复,解除熔断状态:
总结
本篇首先讲解了一下微服务中熔断、降级的基本概念,然后对.Net Core中的Polly
框架做了一个基本介绍,最后基于Polly
演示了如何在.NET Core中实现熔断降级来提高服务质量。而熔断本质上只是一个保护壳,在周围出现异常的时候保全自身,从长远来看,平时定期做好压力测试才能防范于未然,降低触发熔断的次数。如果清楚的知道每个服务的承载量,并做好服务限流的控制,就能将“高压”下触发熔断的概率降到最低了。那下一篇就来介绍一下速率限制(Rate Limiting),敬请期待!
参考资料
ASP.NET Core 微服务初探[2]:熔断降级之Polly的更多相关文章
- ASP.NET Core 微服务初探[1]:服务发现之Consul
ASP.NET Core 微服务初探[1]:服务发现之Consul 在传统单体架构中,由于应用动态性不强,不会频繁的更新和发布,也不会进行自动伸缩,我们通常将所有的服务地址都直接写在项目的配置文件 ...
- 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发
<ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...
- (5)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 熔断降级(Polly)
一. 什么是熔断降级 熔断就是“保险丝”.当出现某些状况时,切断服务,从而防止应用程序不断地尝试执行可能会失败的操作给系统造成“雪崩”,或者大量的超时等待导致系统卡死. 降级的目的是当某个服务提供者发 ...
- (7)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 利用Polly+AOP+依赖注入封装的降级框架
创建简单的熔断降级框架 要达到的目标是: 参与降级的方法参数要一样,当HelloAsync执行出错的时候执行HelloFallBackAsync方法. public class Person { [H ...
- ASP.NET Core微服务+Tabler前端框架搭建个人博客1--开始前想说的话
写在前面 本人为在读研究生,特别喜欢.NET,觉得.NET的编程方式.语法都特别友好,学习.NET Core已经差不多有一年半了,从一开始不知道如何入门到现在终于可以编写一些小的应用程序,想一想还是非 ...
- (1)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型
开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点: 1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块: 2)系统耦合性强,一旦其中一个模块有问题, ...
- (11)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Thrift高效通讯 (完结)
一. 什么是 RPC Restful 采用 Http 进行通讯,优点是开放.标准.简单.兼容性升级容易: 缺点是性能略低.在 QPS 高或者对响应时间要求苛刻的服务上,可以用 RPC(Remote P ...
- (8)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot网关(Api GateWay)
说到现在现有微服务的几点不足: 1) 对于在微服务体系中.和 Consul 通讯的微服务来讲,使用服务名即可访问.但是对于手 机.web 端等外部访问者仍然需要和 N 多服务器交互,需要记忆他们的服务 ...
- (10)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot+Identity Server
用 JWT 机制实现验证的原理如下图: 认证服务器负责颁发 Token(相当于 JWT 值)和校验 Token 的合法性. 一. 相关概念 API 资源(API Resource):微博服务器接口. ...
随机推荐
- 记一次win10+oracle11.2安装
下载安装文件,地址:链接:https://pan.baidu.com/s/1gObmWv5_w2Y4Jlf2-RkBYA 密码:1rx9 安装手册参考:链接:https://pan.baidu.com ...
- UML图之类图(转)
基本概念 类图(Class Diagram): 类图是面向对象系统建模中最常用和最重要的图,是定义其它图的基础.类图主要是用来显示系统中的类.接口以及它们之间的静态结构和关系的一种静态模型. 类图的3 ...
- Mapbox Studio Classic 闪退问题解决方案
之前安装过Mapbox Studio Classic 0.38,好久没有用了,今天用的时候发现不停的闪退,经过一番折腾,发现删除 %USERPROFILE%\.mapbox-studio 目录下所有文 ...
- 关于Android UI 优化
之前项目为了同时兼容tv和手机端的UI,使用了百分比布局来动态计算控件的宽高,这种适配方案只关心屏幕的宽高(分辨率),与屏幕的像素密度无关. 在新的项目里也使用了这种方案.但是由于项目的运行硬件计算能 ...
- hbase-数据恢复流程
引用<https://blog.csdn.net/nigeaoaojiao/article/details/54909921> hlog介绍: hlog构建: 从图中可以看出,对于一个hl ...
- 杨其菊/常惠琢《面向对象程序设计(java)》第十一周学习总结
<面向对象程序设计>第十一周学习总结 第一部分:理论知识 JAVA的集合框架 JAVA的集合框架实现对各种数据结构的封装,以降低对数据管理与处理的难度. 所谓框架就是一个类库的集合,框 ...
- 安装swoole
php需要安装swoole扩展 swoole4.3.2 cd /usr/local/src/ wget https://pecl.php.net/get/swoole-4.3.2.tgz tar -z ...
- Django使用第三方模块django-password-reset重置密码
网上关于django第三方模块django-password-reset重置密码的几篇博客有一个严重的bug 见博客:https://blog.csdn.net/qq_42820268/article ...
- Linux 查看网络状态工具
1. iftop 效果如下图: 界面上面显示的是类似刻度尺的刻度范围,为显示流量图形的长条作标尺用的. 中间的<= =>这两个左右箭头,表示的是流量的方向. TX:发送流量RX:接收流量T ...
- 茶杯头开枪ahk代码
;说明这个工具是为了茶杯头写的,F1表示换枪攻击,F3表示不换枪攻击,F2表示停止攻击. $F1::loop{ GetKeyState, state, F2, Pif state = D{break ...