当我们从单体架构迁移到微服务模式时,其中一个比较大的变化就是模块(业务,服务等)间的调用方式。在以前,一个业务流程的执行在一个进程中就完成了,但是在微服务模式下可能会分散到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,并且其StatusCodeNotFound时,才对其进行处理。更多用法参考: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),敬请期待!

附本篇示例源码地址:https://github.com/RainingNight/AspNetCoreSample/tree/master/src/Microservice/CircuitBreaker/PollyDemo

参考资料

ASP.NET Core 微服务初探[2]:熔断降级之Polly的更多相关文章

  1. ASP.NET Core 微服务初探[1]:服务发现之Consul

    ASP.NET Core 微服务初探[1]:服务发现之Consul   在传统单体架构中,由于应用动态性不强,不会频繁的更新和发布,也不会进行自动伸缩,我们通常将所有的服务地址都直接写在项目的配置文件 ...

  2. 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发

    <ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...

  3. (5)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 熔断降级(Polly)

    一. 什么是熔断降级 熔断就是“保险丝”.当出现某些状况时,切断服务,从而防止应用程序不断地尝试执行可能会失败的操作给系统造成“雪崩”,或者大量的超时等待导致系统卡死. 降级的目的是当某个服务提供者发 ...

  4. (7)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 利用Polly+AOP+依赖注入封装的降级框架

    创建简单的熔断降级框架 要达到的目标是: 参与降级的方法参数要一样,当HelloAsync执行出错的时候执行HelloFallBackAsync方法. public class Person { [H ...

  5. ASP.NET Core微服务+Tabler前端框架搭建个人博客1--开始前想说的话

    写在前面 本人为在读研究生,特别喜欢.NET,觉得.NET的编程方式.语法都特别友好,学习.NET Core已经差不多有一年半了,从一开始不知道如何入门到现在终于可以编写一些小的应用程序,想一想还是非 ...

  6. (1)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型

    开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点: 1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块: 2)系统耦合性强,一旦其中一个模块有问题, ...

  7. (11)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Thrift高效通讯 (完结)

    一. 什么是 RPC Restful 采用 Http 进行通讯,优点是开放.标准.简单.兼容性升级容易: 缺点是性能略低.在 QPS 高或者对响应时间要求苛刻的服务上,可以用 RPC(Remote P ...

  8. (8)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot网关(Api GateWay)

    说到现在现有微服务的几点不足: 1) 对于在微服务体系中.和 Consul 通讯的微服务来讲,使用服务名即可访问.但是对于手 机.web 端等外部访问者仍然需要和 N 多服务器交互,需要记忆他们的服务 ...

  9. (10)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot+Identity Server

    用 JWT 机制实现验证的原理如下图:  认证服务器负责颁发 Token(相当于 JWT 值)和校验 Token 的合法性. 一. 相关概念 API 资源(API Resource):微博服务器接口. ...

随机推荐

  1. 记一次win10+oracle11.2安装

    下载安装文件,地址:链接:https://pan.baidu.com/s/1gObmWv5_w2Y4Jlf2-RkBYA 密码:1rx9 安装手册参考:链接:https://pan.baidu.com ...

  2. UML图之类图(转)

    基本概念 类图(Class Diagram): 类图是面向对象系统建模中最常用和最重要的图,是定义其它图的基础.类图主要是用来显示系统中的类.接口以及它们之间的静态结构和关系的一种静态模型. 类图的3 ...

  3. Mapbox Studio Classic 闪退问题解决方案

    之前安装过Mapbox Studio Classic 0.38,好久没有用了,今天用的时候发现不停的闪退,经过一番折腾,发现删除 %USERPROFILE%\.mapbox-studio 目录下所有文 ...

  4. 关于Android UI 优化

    之前项目为了同时兼容tv和手机端的UI,使用了百分比布局来动态计算控件的宽高,这种适配方案只关心屏幕的宽高(分辨率),与屏幕的像素密度无关. 在新的项目里也使用了这种方案.但是由于项目的运行硬件计算能 ...

  5. hbase-数据恢复流程

    引用<https://blog.csdn.net/nigeaoaojiao/article/details/54909921> hlog介绍: hlog构建: 从图中可以看出,对于一个hl ...

  6. 杨其菊/常惠琢《面向对象程序设计(java)》第十一周学习总结

    <面向对象程序设计>第十一周学习总结 第一部分:理论知识 JAVA的集合框架 JAVA的集合框架实现对各种数据结构的封装,以降低对数据管理与处理的难度. 所谓框架就是一个类库的集合,框 ...

  7. 安装swoole

    php需要安装swoole扩展 swoole4.3.2 cd /usr/local/src/ wget https://pecl.php.net/get/swoole-4.3.2.tgz tar -z ...

  8. Django使用第三方模块django-password-reset重置密码

    网上关于django第三方模块django-password-reset重置密码的几篇博客有一个严重的bug 见博客:https://blog.csdn.net/qq_42820268/article ...

  9. Linux 查看网络状态工具

    1. iftop 效果如下图: 界面上面显示的是类似刻度尺的刻度范围,为显示流量图形的长条作标尺用的. 中间的<= =>这两个左右箭头,表示的是流量的方向. TX:发送流量RX:接收流量T ...

  10. 茶杯头开枪ahk代码

    ;说明这个工具是为了茶杯头写的,F1表示换枪攻击,F3表示不换枪攻击,F2表示停止攻击. $F1::loop{ GetKeyState, state, F2, Pif state = D{break ...