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 错误处理使用三步曲
- 定义条件: 定义你要处理的 错误异常/返回结果
- 定义处理方式 : 重试,熔断,回退
- 执行
var policy = Policy .Handle<SomeExceptionType>() // 定义条件 .Retry(); // 定义处理方式 // 执行 policy.Execute(() => DoSomething());
定义条件
我们可以针对两种情况来定义条件:错误异常和返回结果。
// 单个异常类型 Policy .Handle<HttpRequestException>() // 限定条件的单个异常 Policy .Handle<SqlException>(ex => ex.Number == 1205) // 多个异常类型 Policy .Handle<HttpRequestException>() .Or<OperationCanceledException>() // 限定条件的多个异常 Policy .Handle<SqlException>(ex => ex.Number == 1205) .Or<ArgumentException>(ex => ex.ParamName == "example") // Inner Exception 异常里面的异常类型 Policy .HandleInner<HttpRequestException>() .OrInner<OperationCanceledException>(ex => ex.CancellationToken != myToken)
以及用返回结果来限定
// 返回结果加限定条件 Policy .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound) // 处理多个返回结果 Policy .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError) .OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway) // 处理元类型结果 (用.Equals) Policy .HandleResult<HttpStatusCode>(HttpStatusCode.InternalServerError) .OrResult<HttpStatusCode>(HttpStatusCode.BadGateway) // 在一个policy里面同时处理异常和返回结果。 HttpStatusCode[] httpStatusCodesWorthRetrying = { HttpStatusCode.RequestTimeout, // 408 HttpStatusCode.InternalServerError, // 500 HttpStatusCode.BadGateway, // 502 HttpStatusCode.ServiceUnavailable, // 503 HttpStatusCode.GatewayTimeout // 504 }; HttpResponseMessage result = Policy .Handle<HttpRequestException>() .OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode)) .RetryAsync(...) .ExecuteAsync( /* some Func<Task<HttpResponseMessage>> */ )
定义处理方式
在这里使用的处理方式就是我们最开始说的服务容错模式,我们将介绍以下三种:重试、熔断、回退。
重试
重试很好理解,当发生某种错误或者返回某种结果的时候进行重试。Polly里面提供了以下几种重试机制
- 按次数重试
- 不断重试(直到成功)
- 等待之后按次数重试
- 等待之后不断重试(直到成功)
按次数重试
// 重试1次 Policy .Handle<SomeExceptionType>() .Retry() // 重试3(N)次 Policy .Handle<SomeExceptionType>() .Retry(3) // 重试多次,加上重试时的action参数 Policy .Handle<SomeExceptionType>() .Retry(3, (exception, retryCount) => { // 干点什么,比如记个日志之类的 });
不断重试
// 不断重试,直到成功 Policy .Handle<SomeExceptionType>() .RetryForever() // 不断重试,带action参数在每次重试的时候执行 Policy .Handle<SomeExceptionType>() .RetryForever(exception => { // do something });
等待之后重试
// 重试3次,分别等待1、2、3秒。 Policy .Handle<SomeExceptionType>() .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
当然也可以在每次重试的时候添加一些处理,这里我们可以从上下文中获取一些数据,这些数据在policy启动执行的时候可以传进来。
Policy .Handle<SomeExceptionType>() .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) }, (exception, timeSpan, context) => { // do something });
熔断
熔断也可以被作为当遇到某种错误场景下的一个操作。以下代码展示了当发生2次SomeExceptionType的异常的时候则会熔断1分钟,该操作后续如果继续尝试执行则会直接返回错误 。
Policy .Handle<SomeExceptionType>() .CircuitBreaker(2, TimeSpan.FromMinutes(1));
可以在熔断和恢复的时候定义委托来做一些额外的处理。onBreak会在被熔断时执行,而onReset则会在恢复时执行。
熔断器状态
我们的CircuitBreakPolicy的State定义了当前熔断器的状态,我们也可能调用它的Is
Action<Exception, TimeSpan> onBreak = (exception, timespan) => { ... }; Action onReset = () => { ... }; CircuitBreakerPolicy breaker = Policy .Handle<SomeExceptionType>() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);
Isolate和Reset方法来手动熔断和恢复 。
CircuitState state = breaker.CircuitState;
- Closed 关闭状态,允许执行
- Open 自动打开,执行会被阻断
- Isolate 手动打开,执行会被阻断
- HalfOpen 从自动打开状态恢复中,在熔断时间到了之后从Open状态切换到Closed
// 手动打开熔断器,阻止执行 breaker.Isolate(); // 恢复操作,启动执行 breaker.Reset();
回退(Fallback)
// 如果执行失败则返回UserAvatar.Blank Policy .Handle<Whatever>() .Fallback<UserAvatar>(UserAvatar.Blank) // 发起另外一个请求去获取值 Policy .Handle<Whatever>() .Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar()) // where: public UserAvatar GetRandomAvatar() { ... } // 返回一个指定的值,添加额外的处理操作。onFallback Policy .Handle<Whatever>() .Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) => { // do something });
执行polly policy
为我声明了一个Policy,并定义了它的异常条件和处理方式,那么接下来就是执行它。执行是把我们具体要运行的代码放到Policy里面。
// 执行一个Action var policy = Policy .Handle<SomeExceptionType>() .Retry(); policy.Execute(() => DoSomething());
// 看我们在retry重试时被调用的一个委托,它可以从context中拿到我们在execute的时候传进来的参数 。 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" }} );
Policy .Handle<SqlException>(ex => ex.Number == 1205) .Or<ArgumentException>(ex => ex.ParamName == "example") .Retry() .Execute(() => DoSomething());
protected async Task<T> ExecuteDb<T>(IServiceExecuteContext context, Func<Task<T>> action, string actionName = null, bool retryForever = false) { RetryPolicy policy = null; var policyBuilder = Policy .Handle<SqlException>(ex => ex.Number == 1205); // Handle Deadlock Retry if (retryForever) { policy = policyBuilder.WaitAndRetryForeverAsync((time, retryCtx) => TimeSpan.FromMilliseconds(time * 1000), (ex, time, retryCtx) => { if (time.TotalSeconds > 30) { Log.Error($"{context.GetCurrentLogMessagePrefix()} {actionName}发生死锁正在重试(等待{time.TotalSeconds}秒)(已重试超过30次!!!!请引起重视!!!!)"); } else { Log.Warn($"{context.GetCurrentLogMessagePrefix()} {actionName}发生死锁正在重试(等待{time.TotalSeconds}秒)"); } }); } else { policy = policyBuilder.WaitAndRetryAsync(new[] { TimeSpan.Zero, TimeSpan.FromMilliseconds(1 * 1000), TimeSpan.FromMilliseconds(2 * 1000) }, (ex, time, retryCtx) => { Log.Warn($"{context.GetCurrentLogMessagePrefix()} {actionName}发生死锁正在重试(等待{time.TotalSeconds}秒)"); }); } return await policy.ExecuteAsync(async () => await action()); }
摘自 :https://www.cnblogs.com/jesse2013/p/polly-docs.html