在进入SOA之后,我们的代码从本地方法调用变成了跨机器的通信。任何一个新技术的引入都会为我们解决特定的问题,都会带来一些新的问题。比如网络故障、依赖服务崩溃、超时、服务器内存与CPU等其它问题。正是因为这些问题无法避免,所以我们在进行系统设计、特别是进行分布式系统设计的时候以“Design For Failure”(为失败而设计)为指导原则。把一些边缘场景以及服务之间的调用发生的异常和超时当成一定会发生的情况来预先进行处理。

Design For Failure
1. 一个依赖服务的故障不会严重破坏用户的体验。
2. 系统能自动或半自动处理故障,具备自我恢复能力。

以下是一些经验的服务容错模式

  • 超时与重试(Timeout and Retry)
  • 限流(Rate Limiting)
  • 熔断器(Circuit Breaker)
  • 舱壁隔离(Bulkhead Isolation)
  • 回退(Fallback)

如果想详细了解这几种模式可以参考美团技术团队的总结:服务容错模式。我们今天要讲的是,thanks to the community 多谢社区, Polly已经为我们实现了以上全部的功能。Polly是一个C#实现的弹性瞬时错误处理库(resilience and transient-fault-handling library一直觉得这个英文翻译不是很好) 。在Polly中,对这些服务容错模式分为两类:

  • 错误处理fault handling :重试、熔断、回退
  • 弹性应变resilience:超时、舱壁、缓存

可以说错误处理是当错误已经发生时,防止由于该错误对整个系统造成更坏的影响而设置。而弹性应变,则在是错误发生前,针对有可能发生错误的地方进行预先处理,从而达到保护整个系统的目地。

Polly 错误处理使用三步曲

  • 定义条件: 定义你要处理的 错误异常/返回结果
  • 定义处理方式 : 重试,熔断,回退
  • 执行

先看一个简单的例子

  1. // 这个例子展示了当DoSomething方法执行的时候如果遇到SomeExceptionType的异常则会进行重试调用。
  1. var policy = Policy
  2. .Handle<SomeExceptionType>() // 定义条件
  3. .Retry(); // 定义处理方式
  4.  
  5. // 执行
  6. policy.Execute(() => DoSomething());

定义条件

我们可以针对两种情况来定义条件:错误异常和返回结果。

  1. // 单个异常类型
  2. Policy
  3. .Handle<HttpRequestException>()
  4.  
  5. // 限定条件的单个异常
  6. Policy
  7. .Handle<SqlException>(ex => ex.Number == 1205)
  8.  
  9. // 多个异常类型
  10. Policy
  11. .Handle<HttpRequestException>()
  12. .Or<OperationCanceledException>()
  13.  
  14. // 限定条件的多个异常
  15. Policy
  16. .Handle<SqlException>(ex => ex.Number == 1205)
  17. .Or<ArgumentException>(ex => ex.ParamName == "example")
  18.  
  19. // Inner Exception 异常里面的异常类型
  20. Policy
  21. .HandleInner<HttpRequestException>()
  22. .OrInner<OperationCanceledException>(ex => ex.CancellationToken != myToken)

  

以及用返回结果来限定

  1. // 返回结果加限定条件
  2. Policy
  3. .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)
  4.  
  5. // 处理多个返回结果
  6. Policy
  7. .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
  8. .OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway)
  9.  
  10. // 处理元类型结果 (用.Equals)
  11. Policy
  12. .HandleResult<HttpStatusCode>(HttpStatusCode.InternalServerError)
  13. .OrResult<HttpStatusCode>(HttpStatusCode.BadGateway)
  14.  
  15. // 在一个policy里面同时处理异常和返回结果。
  16. HttpStatusCode[] httpStatusCodesWorthRetrying = {
  17. HttpStatusCode.RequestTimeout, // 408
  18. HttpStatusCode.InternalServerError, // 500
  19. HttpStatusCode.BadGateway, // 502
  20. HttpStatusCode.ServiceUnavailable, // 503
  21. HttpStatusCode.GatewayTimeout // 504
  22. };
  23. HttpResponseMessage result = Policy
  24. .Handle<HttpRequestException>()
  25. .OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
  26. .RetryAsync(...)
  27. .ExecuteAsync( /* some Func<Task<HttpResponseMessage>> */ )

  

定义处理方式

在这里使用的处理方式就是我们最开始说的服务容错模式,我们将介绍以下三种:重试、熔断、回退。

重试

重试很好理解,当发生某种错误或者返回某种结果的时候进行重试。Polly里面提供了以下几种重试机制

  • 按次数重试
  • 不断重试(直到成功)
  • 等待之后按次数重试
  • 等待之后不断重试(直到成功)

按次数重试

  1. // 重试1次
  2. Policy
  3. .Handle<SomeExceptionType>()
  4. .Retry()
  5.  
  6. // 重试3(N)次
  7. Policy
  8. .Handle<SomeExceptionType>()
  9. .Retry(3)
  10.  
  11. // 重试多次,加上重试时的action参数
  12. Policy
  13. .Handle<SomeExceptionType>()
  14. .Retry(3, (exception, retryCount) =>
  15. {
  16. // 干点什么,比如记个日志之类的
  17. });

  

不断重试

  1. // 不断重试,直到成功
  2. Policy
  3. .Handle<SomeExceptionType>()
  4. .RetryForever()
  5.  
  6. // 不断重试,带action参数在每次重试的时候执行
  7. Policy
  8. .Handle<SomeExceptionType>()
  9. .RetryForever(exception =>
  10. {
  11. // do something
  12. });

  

等待之后重试

  1. // 重试3次,分别等待1、2、3秒。
  2. Policy
  3. .Handle<SomeExceptionType>()
  4. .WaitAndRetry(new[]
  5. {
  6. TimeSpan.FromSeconds(1),
  7. TimeSpan.FromSeconds(2),
  8. TimeSpan.FromSeconds(3)
  9. });

  

当然也可以在每次重试的时候添加一些处理,这里我们可以从上下文中获取一些数据,这些数据在policy启动执行的时候可以传进来。

  1. Policy
  2. .Handle<SomeExceptionType>()
  3. .WaitAndRetry(new[]
  4. {
  5. TimeSpan.FromSeconds(1),
  6. TimeSpan.FromSeconds(2),
  7. TimeSpan.FromSeconds(3)
  8. }, (exception, timeSpan, context) => {
  9. // do something
  10. });

  

把WiatAndRetry抱成WaitAndRetryForever()则可以实现重试直到成功。

熔断

熔断也可以被作为当遇到某种错误场景下的一个操作。以下代码展示了当发生2次SomeExceptionType的异常的时候则会熔断1分钟,该操作后续如果继续尝试执行则会直接返回错误 。

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

  

可以在熔断和恢复的时候定义委托来做一些额外的处理。onBreak会在被熔断时执行,而onReset则会在恢复时执行。

  1.  

熔断器状态

我们的CircuitBreakPolicy的State定义了当前熔断器的状态,我们也可能调用它的Is

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

  

olate和Reset方法来手动熔断和恢复 。

  1. CircuitState state = breaker.CircuitState;

  

  • Closed 关闭状态,允许执行
  • Open 自动打开,执行会被阻断
  • Isolate 手动打开,执行会被阻断
  • HalfOpen  从自动打开状态恢复中,在熔断时间到了之后从Open状态切换到Closed
  1. // 手动打开熔断器,阻止执行
  2. breaker.Isolate();
  3. // 恢复操作,启动执行
  4. breaker.Reset();

  

回退(Fallback)

  1. // 如果执行失败则返回UserAvatar.Blank
  2. Policy
  3. .Handle<Whatever>()
  4. .Fallback<UserAvatar>(UserAvatar.Blank)
  5.  
  6. // 发起另外一个请求去获取值
  7. Policy
  8. .Handle<Whatever>()
  9. .Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar()) // where: public UserAvatar GetRandomAvatar() { ... }
  10.  
  11. // 返回一个指定的值,添加额外的处理操作。onFallback
  12. Policy
  13. .Handle<Whatever>()
  14. .Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) =>
  15. {
  16. // do something
  17. });

  

执行polly policy

为我声明了一个Policy,并定义了它的异常条件和处理方式,那么接下来就是执行它。执行是把我们具体要运行的代码放到Policy里面。

  1. // 执行一个Action
  2. var policy = Policy
  3. .Handle<SomeExceptionType>()
  4. .Retry();
  5.  
  6. policy.Execute(() => DoSomething());

  

这就是我们最开始的例子,还记得我们在异常处理的时候有一个context上下文吗?我们可以在执行的时候带一些参数进去

  1. // 看我们在retry重试时被调用的一个委托,它可以从context中拿到我们在execute的时候传进来的参数 。
  2. var policy = Policy
  3. .Handle<SomeExceptionType>()
  4. .Retry(3, (exception, retryCount, context) =>
  5. {
  6. var methodThatRaisedException = context["methodName"];
  7. Log(exception, methodThatRaisedException);
  8. });
  9.  
  10. policy.Execute(
  11. () => DoSomething(),
  12. new Dictionary<string, object>() {{ "methodName", "some method" }}
  13. );

  

当然,我们也可以将Handle,Retry, Execute 这三个阶段都串起来写。

  1. Policy
  2. .Handle<SqlException>(ex => ex.Number == 1205)
  3. .Or<ArgumentException>(ex => ex.ParamName == "example")
  4. .Retry()
  5. .Execute(() => DoSomething());

  

Polly 弹性应变处理Resilience

我们在上面讲了Polly在错误处理方面的使用,接下来我们介绍Polly在弹性应变这块的三个应用: 超时、舱壁和缓存。

超时

  1. Policy
  2. .Timeout(TimeSpan.FromMilliseconds(2500))

  

支持传入action回调

  1. Policy
  2. .Timeout(30, onTimeout: (context, timespan, task) =>
  3. {
  4. // do something
  5. });

  

超时分为乐观超时与悲观超时,乐观超时依赖于CancellationToken ,它假设我们的具体执行的任务都支持CancellationToken。那么在进行timeout的时候,它会通知执行线程取消并终止执行线程,避免额外的开销。下面的乐观超时的具体用法 。

  1. // 声明 Policy
  2. Policy timeoutPolicy = Policy.TimeoutAsync(30);
  3. HttpResponseMessage httpResponse = await timeoutPolicy
  4. .ExecuteAsync(
  5. async ct => await httpClient.GetAsync(endpoint, ct),
  6. CancellationToken.None
  7. // 最后可以把外部的 CacellationToken附加到 timeoutPollcy的 CT上,在这里我们没有附加
  8. );

  

悲观超时与乐观超时的区别在于,如果执行的代码不支持取消CancellationToken,它还会继续执行,这会是一个比较大的开销。

  1. Policy
  2. .Timeout(30, TimeoutStrategy.Pessimistic)

  

上面的代码也有悲观sad...的写法

  1. Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Pessimistic);
  2. var response = await timeoutPolicy
  3. .ExecuteAsync(
  4. async () => await FooNotHonoringCancellationAsync(),
  5. );// 在这里我们没有 任何与CancllationToken相关的处理

  

舱壁

在开头的那篇文章中详细解释了舱壁这种模式,它用来限制某一个操作的最大并发执行数量 。比如限制为12

  1. Policy
  2. .Bulkhead(12)

  

同时,我们还可以控制一个等待处理的队列长度

  1. Policy
  2. .Bulkhead(12, 2)

  

以及当请求执行操作被拒绝的时候,执行回调

  1. Policy
  2. .Bulkhead(12, context =>
  3. {
  4. // do something
  5. });

  

缓存

Polly的缓存需要依赖于一个外部的Provider。

  1. var memoryCacheProvider = new Polly.Caching.MemoryCache.MemoryCacheProvider(MemoryCache.Default);
  2. var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5));
  3.  
  4. // 设置一个绝对的过期时间
  5. var cachePolicy = Policy.Cache(memoryCacheProvider, new AbsoluteTtl(DateTimeOffset.Now.Date.AddDays(1));
  6.  
  7. // 设置一个滑动的过期时间,即每次使用缓存的时候,过期时间会更新
  8. var cachePolicy = Policy.Cache(memoryCacheProvider, new SlidingTtl(TimeSpan.FromMinutes(5));
  9.  
  10. // 我们用Policy的缓存机制来实现从缓存中读取一个值,如果该值在缓存中不存在则从提供的函数中取出这个值放到缓存中。
  11. // 借且于Polly Cache 这个操作只需要一行代码即可。
  12. TResult result = cachePolicy.Execute(() => getFoo(), new Context("FooKey")); // "FooKey" is the cache key used in this execution.
  13.  
  14. // Define a cache Policy, and catch any cache provider errors for logging.
  15. var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5),
  16. (context, key, ex) => {
  17. logger.Error($"Cache provider, for key {key}, threw exception: {ex}."); // (for example)
  18. }
  19. );

  

组合Policy

最后我们要说的是如何将多个policy组合起来。大致的操作是定义多个policy,然后用Wrap方法即可。

  1. var policyWrap = Policy
  2. .Wrap(fallback, cache, retry, breaker, timeout, bulkhead);
  3. policyWrap.Execute(...)

  

在另一个Policy声明时组合使用其它外部声明的Policy。

  1. PolicyWrap commonResilience = Policy.Wrap(retry, breaker, timeout);
  2.  
  3. Avatar avatar = Policy
  4. .Handle<Whatever>()
  5. .Fallback<Avatar>(Avatar.Blank)
  6. .Wrap(commonResilience)
  7. .Execute(() => { /* get avatar */ });

写在后面

上一篇我们介绍了《asp.net core开源api 网关Ocelot的中文使用文档》,Ocelot里面的一些关于Qos服务质量的处理就是用Polly来实现的。当然在没有网关介入的情况 下,我们也可以单独来使用Polly做弹性应对和瞬时错误处理。关于分布式架构,这是一个很大的话题,我们后面继续展示,欢迎关注 。

ASP VNext 开源服务容错处理库Polly使用文档的更多相关文章

  1. 容错处理库Polly使用文档

    Design For Failure1. 一个依赖服务的故障不会严重破坏用户的体验.2. 系统能自动或半自动处理故障,具备自我恢复能力. 以下是一些经验的服务容错模式 超时与重试(Timeout an ...

  2. 服务容错处理库Polly使用

    服务容错处理库Polly使用 在进入SOA之后,我们的代码从本地方法调用变成了跨机器的通信.任何一个新技术的引入都会为我们解决特定的问题,都会带来一些新的问题.比如网络故障.依赖服务崩溃.超时.服务器 ...

  3. 如何实现SP文档库类似百度文档库的效果 (副标题:如何在SP2013文档库的SWF文件用FlexPager显示)

    1. 编辑文档库列表显示页面,如下图: 2. 添加内容编辑器,如下图: 3. 添加如下在[内容编辑器中]-[编辑源],添加如下JS代码,如下图: ​ 代码如下: <scrip type=&quo ...

  4. asp.net core使用Swashbuckle.AspNetCore(swagger)生成接口文档

    asp.net core中使用Swashbuckle.AspNetCore(swagger)生成接口文档 Swashbuckle.AspNetCore:swagger的asp.net core实现 项 ...

  5. 转:在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档

    在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档 一直以来,我都想为 PDF 补丁丁添加一个 PDF 渲染引擎.可是,目前并没有可以在 .NET 框架上运行的免费 PDF ...

  6. Excel催化剂开源第22波-VSTO的帮助文档在哪里?

    Excel催化剂开源第22波-VSTO的帮助文档在哪里? Excel催化剂   2019.01.12 14:10 字数 2930 阅读 55评论 0喜欢 0 编辑文章 对于专业程序猿来说,查找文档不是 ...

  7. Oracle服务端及客户端搭建帮助文档

    Oracle服务端及客户端搭建帮助文档 目录 简介 Oracle服务端安装 Oracle客户端安装 PLSQL安装 登录测试 系统配置修改 用户操作 解锁账户.密码 创建账户及密码 配置监听文件 监听 ...

  8. 使用Python爬虫库BeautifulSoup遍历文档树并对标签进行操作详解(新手必学)

    为大家介绍下Python爬虫库BeautifulSoup遍历文档树并对标签进行操作的详细方法与函数下面就是使用Python爬虫库BeautifulSoup对文档树进行遍历并对标签进行操作的实例,都是最 ...

  9. https://github.com/coolnameismy/BabyBluetooth github上的一个ios 蓝牙4.0的库并带文档和教程

    The easiest way to use Bluetooth (BLE )in ios,even bady can use. 简单易用的蓝牙库,基于CoreBluetooth的封装,并兼容ios和 ...

随机推荐

  1. 【mysql】mysql主从复制

    mysql主从复制配置 主服务器:192.168.0.100 从服务器 192.168.0.101 主服务器配置 my.ini(window下 linux 下是my.cnf) #开启二进制日志 log ...

  2. Filter的注册2

    既然Filter是一种COM组件,使用前就必须先注册.Filter的注册程序为regsvr32.exe (位于操作系统目录的system32子目录下).假设现在有一个Filter文件,它的完整路径为C ...

  3. angular路由操作

    在单页面应用程序中比如angular应用,我们需要根据url的变化(即:不同的请求),来分配不同的资源.根据请求的URL来决定执行哪个模块,这个过程叫路由,同时,我们需要设计路由规则. 下面给出一个简 ...

  4. springboot集成Actuator

    Actuator监控端点,主要用来监控与管理. 原生端点主要分为三大类:应用配置类.度量指标类.操作控制类. 应用配置类:获取应用程序中加载的配置.环境变量.自动化配置报告等与SpringBoot应用 ...

  5. 【BZOJ4556】字符串(后缀数组,主席树)

    [BZOJ4556]字符串(后缀数组,主席树) 题面 BZOJ 题解 注意看题: 要求的是\([a,b]\)的子串和[c,d]的\(lcp\)的最大值 先来一下暴力吧 求出\(SA\)之后 暴力枚举\ ...

  6. 【Luogu1291】百事世界杯之旅(动态规划,数学期望)

    [Luogu1291]百事世界杯之旅(动态规划,数学期望) 题面 洛谷 题解 设\(f[i]\)表示已经集齐了\(i\)个名字的期望 现在有两种方法: 先说我自己的: \[f[i]=f[i-1]+1+ ...

  7. 2014NOIP普及组 子矩阵

    觉得题目水的离开 觉得普及组垃圾的请离开 不知道 DFS 和 DP 的请离开 不屑的大佬请离开 ……. 感谢您贡献的访问量 ————————————————华丽的分割线 ——————————————— ...

  8. java 压缩文件

    package folder; import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundExcept ...

  9. 关系型数据库工作原理-数据特征统计分析(翻译自Coding-Geek文章)

    本文翻译自Coding-Geek文章:< How does a relational database work>.原文链接:http://coding-geek.com/how-data ...

  10. mysql整理

    一.建表(创建一个简单的用户权限关系表) 1.user(用户表) CREATE TABLE `user` ( `username` ) NOT NULL, `password` ) DEFAULT N ...