【微服务学习】Polly:熔断降级组件

何为熔断降级

  “熔断器如同电力过载保护器。它可以实现快速失败,如果它在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,不再访问远程服务器,从而防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费时间去等到长时间的超时产生。”   降级的目的是当某个服务提供者发生故障的时候,向调用方返回一个替代响应。

  简单一句话概括,降级就是在调用的下游服务A出现问题(常见超时),提供PLAN-B,返回的效果可能没有服务A好,但是聊胜于无。而熔断器的存在就是要保障何时走到降级方法,何时恢复,以什么样的策略恢复。

.NET Core 熔断降级实践

简介

  Polly是一种.NET弹性和瞬态故障处理库,允许我们以非常顺畅和线程安全的方式来执诸如行重试,断路,超时,故障恢复等策略。  

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 and UWP targets).上,同时也为旧版本的.NET Framework提供了一些可用的旧版本,具体版本对应如下:

  该项目作者现已成为.NET基金会一员,项目一直在不停迭代和更新,项目地址【https://github.com/App-vNext/Polly】。

七种恢复策略

策略前置条件此策略解决什么问题?
重试策略(Retry) 
(policy family)
(快速开始 ; 深入学习)
重试策略针对的前置条件是短暂的故障延迟且在短暂的延迟之后能够自我纠正。"也许这只是昙花一现"允许我们做的是能够自动配置重试机制。
断路器(Circuit-breaker)
(policy family)
(快速开始 ; 深入学习)
断路器策略针对的前置条件是当系统繁忙时,快速响应失败总比让用户一直等待更好。

保护系统故障免受过载,Polly可以帮其恢复。

"痛了,自然就会放下"

"让它歇一下"

当故障超过某个预先配置的阈值时, 中断电路 (块执行) 一段时间。
超时(Timeout)
(快速开始 ; 深入学习)
超时策略针对的前置条件是超过一定的等待时间,想要得到成功的结果是不可能的。"你不必等待,她不会再来"保证调用者不必等待太长时间。
隔板隔离(Bulkhead Isolation)
(快速开始 ; 深入学习)
隔板隔离针对的前置条件是当进程出现故障时,多个失败一直在主机中对资源(例如线程/ CPU)一直占用。下游系统故障也可能导致上游失败。

这两个风险都将造成严重的后果。

"一颗老鼠屎坏了一锅汤"将受管制的操作限制在固定的资源池中,避免其他资源受其影响。
缓存(Cache)
(快速开始 ; 深入学习)
数据不会很频繁的进行更新,相同请求的响应是相似的。"听说 
你还会再来
我翘首以盼"
首次加载数据时将响应数据进行缓存,请求时若缓存中存在则直接从缓存中读取。
回退(Fallback)
(快速开始 ; 深入学习)
操作将仍然失败 - 但是你可以实现准备好失败后要做的补救措施。"你若安好,我备胎到老。"定义失败时要返回 (或要执行的操作) 的替代值。.
策略包装(PolicyWrap)
(快速开始 ; 深入学习)
不同的故障需要不同的策略,也就意味着弹性灵活使用组合。"谋定而后动"允许灵活地组合上述任何策略。

实践

故障处理(被动策略)

故障处理策略处理通过策略执行的代码所引发的特定的异常或返回结果。

第一步:指定希望处理的异常(可选-指定要处理的返回结果)

指定希望处理的异常:
// 单一异常种类
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")

// 普通异常或聚合异常的内部异常, 可以带有条件
Policy
.HandleInner<HttpRequestException>()
.OrInner<OperationCanceledException>(ex => ex.CancellationToken != myToken)
指定要处理的返回结果

从Polly v4.3.0起,包含返回TResult的调用的策略也可以处理TResult返回值

// 带条件判断的单种返回值处理
Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)

// 带条件判断的多种返回值处理
Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)

// 原始返回值处理 (隐式调用 .Equals())
Policy
.HandleResult<HttpStatusCode>(HttpStatusCode.InternalServerError)
.OrResult(HttpStatusCode.BadGateway)

// 在一个策略中同时处理异常和返回值
HttpStatusCode[] httpStatusCodesWorthRetrying = {
HttpStatusCode.RequestTimeout, // 408
HttpStatusCode.InternalServerError, // 500
HttpStatusCode.BadGateway, // 502
HttpStatusCode.ServiceUnavailable, // 503
HttpStatusCode.GatewayTimeout // 504
};
HttpResponseMessage result = await Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
.RetryAsync(...)
.ExecuteAsync( /* Func<Task<HttpResponseMessage>> */ )

第二步:指定策略应如何处理这些错误

重试(Retry)
// 重试一次
Policy
.Handle<SomeExceptionType>()
.Retry()

// 重试多次
Policy
.Handle<SomeExceptionType>()
.Retry(3)

// 重试多次,每次重试触发事件(参数为此次异常和当前重试次数)
Policy
.Handle<SomeExceptionType>()
.Retry(3, onRetry: (exception, retryCount) =>
{
// do something
});

// 重试多次,每次重试触发事件(参数为此次异常、当前重试次数和当前执行的上下文)
Policy
.Handle<SomeExceptionType>()
.Retry(3, onRetry: (exception, retryCount, context) =>
{
// do something
});
不断重试直到成功(Retry forever until succeeds)
// 不断重试
Policy
.Handle<SomeExceptionType>()
.RetryForever()

// 不断重试,每次重试触发事件(参数为此次异常)
Policy
.Handle<SomeExceptionType>()
.RetryForever(onRetry: exception =>
{
// do something
});

// 不断重试,每次重试触发事件(参数为此次异常和当前执行的上下文)
Policy
.Handle<SomeExceptionType>()
.RetryForever(onRetry: (exception, context) =>
{
// do something
});
等待并重试(Wait and retry)

WaitAndRetry策略处理HTTP状态代码429的重试后状态

// 重试多次, 每次重试之间等待指定的持续时间。(失败之后触发等待, 然后再进行下一次尝试。)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
});

// 重试并触发事件多次, 每次重试之间等待指定的持续时间。(事件参数为当前异常和时间间隔)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
}, (exception, timeSpan) => {
// do something
});

// 重试并触发事件多次, 每次重试之间等待指定的持续时间。(事件参数为当前异常、时间间隔和当前执行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
}, (exception, timeSpan, context) => {
// do something
});

// 重试并触发事件多次, 每次重试之间等待指定的持续时间。(事件参数为当前异常、时间间隔、当前重试次数和当前执行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
}, (exception, timeSpan, retryCount, context) => {
// do something
});

// 重试指定的次数, 根据当前重试次数计算等待时间 (允许指数回退)
// 当前这种情况下, 等待时间为:
// 2 ^ 1 = 2 s
// 2 ^ 2 = 4 s
// 2 ^ 3 = 8 s
// 2 ^ 4 = 16 s
// 2 ^ 5 = 32 s
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(5, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
);

// 重试指定的次数,每次重试时触发事件,根据当前重试次数计算等待时间。(事件参数为当前异常、时间间隔和当前执行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(
5,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, context) => {
// do something
}
);

// 重试指定的次数,每次重试时触发事件,根据当前重试次数计算等待时间。(事件参数为当前异常、时间间隔、当前重试次数和当前执行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(
5,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, retryCount, context) => {
// do something
}
);
不断等待并重试直到成功(Wait and retry forever until succeeds)

如果所有重试都失败, 重试策略将重新引发最后一个异常返回到调用代码。

// 不断等待并重试
Policy
.Handle<SomeExceptionType>()
.WaitAndRetryForever(retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
);

// 不断等待并重试,每次重试时触发事件。(事件参数为当前异常、时间间隔)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetryForever(
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timespan) =>
{
// do something
});


// 不断等待并重试,每次重试时触发事件。(事件参数为当前异常、时间间隔和当前执行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetryForever(
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timespan, context) =>
{
// do something
});
断路器(Circuit-breaker)

断路器策略通过在程序出错时抛出BrokenCircuitException来屏蔽其他异常。文档 请注意, 断路器策略将重新引发所有异常, 甚至是已处理的异常。所以使用时通常会将重试策略和断路器策略组合使用

// 在指定数量的连续异常后断开程序执行并在之后的一段时间内保持程序执行断开。
Policy
.Handle<SomeExceptionType>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1));

// 在指定数量的连续异常后断开程序执行并在之后的一段时间内保持程序执行断开。当程序执行断开或者重新启用时触发事件。(程序执行断开事件参数为当前异常和间隔时间,重新启用事件无参数)
Action<Exception, TimeSpan> onBreak = (exception, timespan) => { ... };
Action onReset = () => { ... };
CircuitBreakerPolicy breaker = Policy
.Handle<SomeExceptionType>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

// 在指定数量的连续异常后断开程序执行并在之后的一段时间内保持程序执行断开。当程序执行断开或者重新启用时触发事件。(程序执行断开事件参数为当前异常、间隔时间和当前执行上下文,重新启用事件参数为当前执行上下文)
Action<Exception, TimeSpan, Context> onBreak = (exception, timespan, context) => { ... };
Action<Context> onReset = context => { ... };
CircuitBreakerPolicy breaker = Policy
.Handle<SomeExceptionType>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

// 程序运行状态, 运行状况。
CircuitState state = breaker.CircuitState;

/*
CircuitState.Closed - 断路器未触发,允许操作执行。
CircuitState.Open - 断路器开启,阻止操作执行。
CircuitState.HalfOpen - 断路器开启指定时间后重新关闭,此状态允许操作执行,之后的开启或关闭取决于继续执行的结果。
CircuitState.Isolated - 断路器被主动开启,阻止操作执行。
*/

// 手动打开 (并保持打开) 断路器(例如需要主动隔离下游服务时)
breaker.Isolate();
// 重置断路器为关闭状态, 再次开始允许操作执行。
breaker.Reset();
高级断路器(Advanced Circuit Breaker)
// 在采样持续时间内, 如果已处理异常的操作的比例超过故障阈值且该时间段内通过请求操作数达到最小吞吐量,主动启动断路器。

Policy
.Handle<SomeExceptionType>()
.AdvancedCircuitBreaker(
failureThreshold: 0.5, // 当>=50%的操作会导致已处理的异常时中断程序。
samplingDuration: TimeSpan.FromSeconds(10), // 采样时间区间为10秒
minimumThroughput: 8, // ... 在采样时间区间内进行了至少8次操作。
durationOfBreak: TimeSpan.FromSeconds(30) // 断路30秒.
);

// 采用状态更改委托的配置重载同样可用于高级断路器。

// 电路状态监控和手动控制同样也可用于高级断路器。

更多相关资料请参考: 文档

有关断路器模式的更多信息, 请参见:

  • 改造 Netflix API 增加接口弹性

  • 断路器浅谈 (马丁·福勒)

  • 断路器模式 (Microsoft)

  • 原始断路器链

回退策略(Fallback)
// 执行错误时提供替代值。
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank)

// 执行错误时使用回调函数提供替代值。
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar()) // where: public UserAvatar GetRandomAvatar() { ... }

// 执行错误时提供替代值的同时触发事件。(事件参数为当前异常信息和当前运行上下文)
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) =>
{
// do something
});

第三步:执行策略

// 执行操作
var policy = Policy
.Handle<SomeExceptionType>()
.Retry();

policy.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" }}
);

// 执行返回结果的函数
var policy = Policy
.Handle<SomeExceptionType>()
.Retry();

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

// 执行传递任意上下文数据且返回结果的操作
var policy = Policy
.Handle<SomeExceptionType>()
.Retry(3, (exception, retryCount, context) =>
{
object methodThatRaisedException = context["methodName"];
Log(exception, methodThatRaisedException)
});

var result = 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());

为了简单起见, 上面的示例显示了策略定义, 然后是策略执行。但是在代码库和应用程序生命周期中, 策略定义和执行可能同样经常被分离。例如, 可以选择在启动时定义策略, 然后通过依赖注入将其提供给使用点。

故障处理(主动策略)

主动策略添加了不基于当策略被引发或返回时才处理错误的弹性策略。

第一步:配置

超时(Timeout)
乐观超时(Optimistic timeout)

乐观超时通过 CancellationToken 运行, 并假定您执行支持合作取消的委托。您必须使用 Execute/Async(...) 重载以获取 CancellationToken, 并且执行的委托必须遵守该 CancellationToken

// 如果执行的委托尚未完成,在调用30秒后超时并返回 。乐观超时: 委托应采取并遵守 CancellationToken。
Policy
.Timeout(30)

// 使用 TimeSpan 配置超时。
Policy
.Timeout(TimeSpan.FromMilliseconds(2500))

// 通过方法提供可变的超时。
Policy
.Timeout(() => myTimeoutProvider)) // Func<TimeSpan> myTimeoutProvider

// 超时后触发事件。(事件参数为当前执行上下文、执行间隔、当前执行的TASK)
Policy
.Timeout(30, onTimeout: (context, timespan, task) =>
{
// do something
});

// 示例:在超时后记录日志
Policy
.Timeout(30, onTimeout: (context, timespan, task) =>
{
logger.Warn($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds.");
});

// 示例:在超时任务完成时捕获该任务中的任何异常
Policy
.Timeout(30, onTimeout: (context, timespan, task) =>
{
task.ContinueWith(t => {
if (t.IsFaulted) logger.Error($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds, with: {t.Exception}.");
});
});

示例执行:

Policy timeoutPolicy = Policy.TimeoutAsync(30);
HttpResponseMessage httpResponse = await timeoutPolicy
.ExecuteAsync(
async ct => await httpClient.GetAsync(endpoint, ct), // 执行一个有参数且响应 CancellationToken 的委托。
CancellationToken.None // 在这种情况下, CancellationToken.None 将被传递到执行中, 这表明您没有将期望的令牌控制通过超时策略添加。自定义 CancellationToken 也可以通过,详情请参阅 wiki 中的例子。
);
悲观超时(Pessimistic timeout)

悲观超时允许调用代码 "离开" 等待执行完成的委托, 即使它不支持取消。在同步执行中, 这是以牺牲一个额外的线程为代价的。有关更多细节, 请参见文档。示例执行:

Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Pessimistic);
var response = await timeoutPolicy
.ExecuteAsync(
async () => await FooNotHonoringCancellationAsync(), // 执行不接受取消令牌且不响应取消的委托。
);

超时策略在发生超时时引发 TimeoutRejectedException。更多详情参见文档。

隔板(Bulkhead)
// 通过该策略将执行限制为最多12个并发操作。
Policy
.Bulkhead(12)

// 将通过策略执行的操作限制为最多12个并发操作, 如果插槽都被占满, 最多可以有两个操作被等待执行。
Policy
.Bulkhead(12, 2)

// 限制并发执行, 如果执行被拒绝, 则调用触发事件。(事件参数为当前执行上下文)
Policy
.Bulkhead(12, context =>
{
// do something
});

// 查看隔板可用容量, 例如健康负荷。
var bulkhead = Policy.Bulkhead(12, 2);
// ...
int freeExecutionSlots = bulkhead.BulkheadAvailableCount;
int freeQueueSlots = bulkhead.QueueAvailableCount;

当隔板策略的插槽全部被正在执行的操作占满是,会引发 BulkheadRejectedException。更多详情参见文档。

缓存(Cache)
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var memoryCacheProvider = new MemoryCacheProvider(memoryCache);
var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5));

// .NET Core CacheProviders DI 示例 请参照以下文章 https://github.com/App-vNext/Polly/wiki/Cache#working-with-cacheproviders :
// - https://github.com/App-vNext/Polly.Caching.MemoryCache
// - https://github.com/App-vNext/Polly.Caching.IDistributedCache

// 定义每天午夜绝对过期的缓存策略。
var cachePolicy = Policy.Cache(memoryCacheProvider, new AbsoluteTtl(DateTimeOffset.Now.Date.AddDays(1));

// 定义超时过期的缓存策略: 每次使用缓存项时, 项目的有效期为5分钟。
var cachePolicy = Policy.Cache(memoryCacheProvider, new SlidingTtl(TimeSpan.FromMinutes(5));

// 定义缓存策略, 并捕获任何缓存提供程序错误以进行日志记录。
var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5),
(context, key, ex) => {
logger.Error($"Cache provider, for key {key}, threw exception: {ex}."); // (for example)
}
);

// 以直通缓存的身份执行缓存: 首先检查缓存;如果未找到, 请执行基础委托并将结果存储在缓存中。
// 用于特定执行的缓存的键是通过在传递给执行的上下文实例上设置操作键 (v6 之前: 执行键) 来指定的。使用下面显示的窗体的重载 (或包含相同元素的更丰富的重载)。
// 示例: "fookey" 是将在下面的执行中使用的缓存密钥。
TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey"));

有关使用其他缓存提供程序的更丰富的选项和详细信息, 请参阅:文档

策略包装(PolicyWrap)
// 定义由以前定义的策略构建的组合策略。
var policyWrap = Policy
.Wrap(fallback, cache, retry, breaker, timeout, bulkhead);
// (包装策略执行任何被包装的策略: fallback outermost ... bulkhead innermost)
policyWrap.Execute(...)

// 定义标准的弹性策略
PolicyWrap commonResilience = Policy.Wrap(retry, breaker, timeout);

// ... 然后包装在额外的策略特定于一个请求类型:
Avatar avatar = Policy
.Handle<Whatever>()
.Fallback<Avatar>(Avatar.Blank)
.Wrap(commonResilience)
.Execute(() => { /* get avatar */ });

// 共享通用弹性, 但将不同的策略包装在另一个请求类型中:
Reputation reps = Policy
.Handle<Whatever>()
.Fallback<Reputation>(Reputation.NotAvailable)
.Wrap(commonResilience)
.Execute(() => { /* get reputation */ });

更多详情参见文档

无策略(NoOp)
// 定义一个策略, 该策略将简单地导致传递给执行的委托 "按原样" 执行。
// 适用于在单元测试中或在应用程序中可能需要策略, 但您只是希望在没有策略干预的情况下通过执行的应用程序。
NoOpPolicy noOp = Policy.NoOp();

更多详情参见文档

第二步:执行策略

同上

执行后:捕获结果或任何最终异常

使用 ExecuteAndCapture(...) 方法可以捕获执行的结果: 这些方法返回一个执行结果实例, 该实例描述的是成功执行还是错误。

var policyResult = await Policy
.Handle<HttpRequestException>()
.RetryAsync()
.ExecuteAndCaptureAsync(() => DoSomethingAsync());
/*
policyResult.Outcome - 调用是成功还是失败
policyResult.FinalException - 最后一个异常。如果调用成功, 则捕获的最后一个异常将为 null
policyResult.ExceptionType - 定义为要处理的策略的最后一个异常 (如上面的 HttpRequestException) 或未处理的异常 (如 Exception). 如果调用成功, 则为 null。
policyResult.Result - 如果执行 func, 调用成功则返回执行结果, 否则为类型的默认值
*/

处理返回值和 Policy<TResult>

如步骤1b 所述, 从 polly v4.3.0 开始, 策略可以组合处理返回值和异常:

// 在一个策略中处理异常和返回值
HttpStatusCode[] httpStatusCodesWorthRetrying = {
HttpStatusCode.RequestTimeout, // 408
HttpStatusCode.InternalServerError, // 500
HttpStatusCode.BadGateway, // 502
HttpStatusCode.ServiceUnavailable, // 503
HttpStatusCode.GatewayTimeout // 504
};
HttpResponseMessage result = await Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
.RetryAsync(...)
.ExecuteAsync( /* some Func<Task<HttpResponseMessage>> */ )

要处理的异常和返回结果可以以任意顺序流畅的表达。

强类型 Policy<TResult>

配置策略 .HandleResult<TResult>(...) 或.OrResult<TResult>(...) 生成特定强类型策略 Policy<TResult>,例如 Retry<TResult>, AdvancedCircuitBreaker<TResult>。这些策略必须用于执行返回 TResult 的委托, 即:

  • Execute(Func<TResult>) (and related overloads)

  • ExecuteAsync(Func<CancellationToken, Task<TResult>>) (and related overloads)

ExecuteAndCapture<TResult>()

.ExecuteAndCapture(...) 在非泛型策略上返回具有属性的 PolicyResult:

policyResult.Outcome - 调用是成功还是失败
policyResult.FinalException - 最后一个异常。如果调用成功, 则捕获的最后一个异常将为 null
policyResult.ExceptionType - 定义为要处理的策略的最后一个异常 (如上面的 HttpRequestException) 或未处理的异常 (如 Exception). 如果调用成功, 则为 null。
policyResult.Result - 如果执行 func, 调用成功则返回执行结果, 否则为类型的默认值

.ExecuteAndCapture<TResult>(Func<TResult>)在强类型策略上添加了两个属性:

policyResult.FaultType - 最终的故障是处理异常还是由策略处理的结果?如果委托执行成功, 则为 null。
policyResult.FinalHandledResult - 处理的最终故障结果;如果调用成功将为空或类型的默认值。

Policy<TResult>策略的状态更改事件

在仅处理异常的非泛型策略中, 状态更改事件 (如 onRetry 和 onBreak ) 提供 Exception 参数。在处理 TResult 返回值的通用性策略中, 状态更改委托是相同的, 除非它们采用 DelegateResult参数代替异常。DelegateResult具有两个属性:

  • Exception // 如果策略正在处理异常则为则刚刚引发异常(否则为空),

  • Result // 如果策略正在处理结果则为刚刚引发的 TResult (否则为 default(TResult))

BrokenCircuitException<TResult>

非通用的循环断路器策略在断路时抛出一个BrokenCircuitException。此 BrokenCircuitException 包含最后一个异常 (导致中断的异常) 作为 InnerException。关于 CircuitBreakerPolicy<TResult> 策略:

  • 由于异常而中断将引发一个 BrokenCircuitException, 并将 InnerException 设置为触发中断的异常 (如以前一样)。

  • 由于处理结果而中断会引发 'BrokenCircuitException<TResult>', 其 Result 属性设置为导致电路中断的结果.

Policy Keys 与 Context data

// 用扩展方法 WithPolicyKey() 使用 PolicyKey 识别策略,
// (例如, 对于日志或指标中的相关性)

var policy = Policy
.Handle<DataAccessException>()
.Retry(3, onRetry: (exception, retryCount, context) =>
{
logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.ExecutionKey}, due to: {exception}.");
})
.WithPolicyKey("MyDataAccessPolicy");

// 在上下文中传递 ExecutionKey , 并使用 ExecutionKey 标识呼叫站点
var customerDetails = policy.Execute(myDelegate, new Context("GetCustomerDetails"));

// "MyDataAccessPolicy" -> context.PolicyKey
// "GetCustomerDetails -> context.ExecutionKey


// 将其他自定义信息从调用站点传递到执行上下文中
var policy = Policy
.Handle<DataAccessException>()
.Retry(3, onRetry: (exception, retryCount, context) =>
{
logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.ExecutionKey}, getting {context["Type"]} of id {context["Id"]}, due to: {exception}.");
})
.WithPolicyKey("MyDataAccessPolicy");

int id = ... // 客户id
var customerDetails = policy.Execute(context => GetCustomer(id),
new Context("GetCustomerDetails", new Dictionary<string, object>() {{"Type","Customer"},{"Id",id}}

更多资料参考文档

PolicyRegistry

// 创建策略注册表 (例如在应用程序启动时)
PolicyRegistry registry = new PolicyRegistry();

// 使用策略填充注册表
registry.Add("StandardHttpResilience", myStandardHttpResiliencePolicy);
// 或者:
registry["StandardHttpResilience"] = myStandardHttpResiliencePolicy;

// 通过 DI 将注册表实例传递给使用站点
public class MyServiceGateway
{
public void MyServiceGateway(..., IReadOnlyPolicyRegistry<string> registry, ...)
{
...
}
}
// (或者, 如果您更喜欢环境上下文模式, 请使用线程安全的单例)

// 使用注册表中的策略
registry.Get<IAsyncPolicy<HttpResponseMessage>>("StandardHttpResilience")
.ExecuteAsync<HttpResponseMessage>(...)

策略注册表具有一系列进一步的类似字典的语义, 例如 .ContainsKey(...), .TryGet(...), .Count, .Clear(), 和 Remove(...),适用于 v5.2.0 以上版本

有关详细信息, 请参阅: 文档

.NET Core 使用Polly重试机制

    public class PollyController : ApiController
{
public readonly RetryPolicy<HttpResponseMessage> _httpRequestPolicy;
public PollyController()
{
_httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
r => r.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromSeconds(retryAttempt));
}
public async Task<IHttpActionResult> Get()
{
var httpClient = new HttpClient();
var requestEndpoint = "http://www.baidu.com";

HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));

IEnumerable<string> numbers = await httpResponse.Content.ReadAsAsync<IEnumerable<string>>();

return Ok(numbers);
}
}

原文地址:https://www.cnblogs.com/WayneShao/p/10672101.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 
640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316235.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

A - Junk-Mail Filter HDU - 2473

只是这样合并&#xff0c;分割点的时候就不能分了。 这样合并再加上虚拟节点&#xff0c;那么每个你要求的节点就的下面就不连其他节点了&#xff0c;这样就可以进行删除操作了 #include<iostream> #include<algorithm> #include<cstdio> #include<cstr…

为什么不要使用 async void

问题在使用 Abp 框架的后台作业时&#xff0c;当后台作业抛出异常&#xff0c;会导致整个程序崩溃。在 Abp 框架的底层执行后台作业的时候&#xff0c;有 try/catch 语句块用来捕获后台任务执行时的异常&#xff0c;但是在这里没有生效。原始代码如下&#xff1a;public class …

张队长主讲这堂 .NET Core技术培训公开课,太原你约不约

这堂.NET Core技术培训课&#xff0c;你不能错过各位开发者朋友们想必也能体会到&#xff0c;现在市面上关于.NET Core的培训课程少之又少&#xff0c;其中有质量有内容的课程更是凤毛麟角&#xff0c;良师难遇&#xff0c;一课难求。但是现在&#xff0c;机会来了。中微云孵邀…

微软推出新语言Bosque,超越结构化程序设计

微软近期推出了一款全新的编程语言 Bosque&#xff0c;该语言参考了 TypeScript 的语法与类型&#xff0c;还有 ML 和 Node/JavaScript 的语义。作者微软计算机科学家 Mark Marron 致力于消除编程过程中出现的各种复杂情况&#xff0c;创造出了他认为超越主流结构化程序设计的 …

vue 实验报告8 基于Nuxt.js开发一个Vue程序,实现登录和注册切换

一、步骤&#xff1a; 保证node.js版本在14以上 1. 全局安装create-nuxt-app: npm install -g create-nuxt-app2.9.x 2. 创建项目: create-nuxt-app my-nuxt-demo 选项这么选&#xff1a; 然后输入&#xff1a; cd my-nuxt-demo 3. 创建登录和注册页面: 在/pages目录下创建logi…

解决vs2019中暂时无法为.net core WinForms使用 Designer 的临时方法

以下方法来自于微软github开源项目WinForms:dotnet/winforms - Using the Classic WinForms Designer in WinForms Core, 请放心使用 .目前.net core下的 Windows Forms的可视化设计器(Designer)尚不可用&#xff0c;后续的Visual Studio 2019 Update才会支持该部分的功能。不过…

P2480 [SDOI2010]古代猪文(数论好题)

P2480 [SDOI2010]古代猪文 题意&#xff1a; 给你n和g&#xff0c;求g∑d∣nCndmodpg^{\sum_{d|n}C_{n}^{d}}\bmod pg∑d∣n​Cnd​modp p999911659 题解&#xff1a; 这个一个综合性很强的数论题 涉及到欧拉定理&#xff0c;Lucas定理&#xff0c;中国剩余定理&#xff0c…

ASP.NET Core开发者成长路线图

来源: MoienTajik/AspNetCore-Developer-Roadmap.2019年ASP.NET Core开发者指南:你可以在下面找到一张图&#xff0c;该图展示了你可以选取的路径及你想学习的库&#xff0c;从而成为一名 ASP.NET Core 开发者。“作为 ASP.NET Core 开发者&#xff0c;我接下来应该学习什么&am…

.NET Framework VS .NET Core

本文对应的原文来至 c-sharpcorner 的一篇文章&#xff0c;文末有链接。如有错误&#xff0c;还请指正。前言你会为你的下一个应用程序选择哪一种开发平台 - .NET Framework 或者 .NET Core&#xff1f;在这篇文章中&#xff0c;让我们比较一下这两个开发平台的特点&#xff0c…

解决 VS2019 中.net core WPF 暂时无法使用 Designer 的临时方法

以下方法来自于微软github开源项目WPF:dotnet/samples - WPF Hello World sample with linked files&#xff0c;请放心使用。此篇文章是上篇文章解决vs2019中暂时无法为.net core WinForms使用 Designer 的临时方法的姊妹篇&#xff0c;但对WPF而言实现起来比WinForms简单很多…

.NET Core 迁移躺坑记

最近将自己负责的一个核心接口系统从.Net Framework迁移到了.Net Core。整体过程&#xff0c;从业务层面说一般般吧(整体还好但还是搞的业务有感&#xff0c;没出严重故障&#xff09;但是技术层面上感觉其实并没有达到要求&#xff0c;不过预期也是应该不会那么顺利&#xff0…

[Abp vNext 源码分析] - 2. 模块系统的变化

一、简要说明本篇文章主要分析 Abp vNext 当中的模块系统&#xff0c;从类型构造层面上来看&#xff0c;Abp vNext 当中不再只是单纯的通过 AbpModuleManager 来管理其他的模块&#xff0c;它现在则是 IModuleManager 和 IModuleLoader 来协同工作&#xff0c;其他的代码逻辑并…

P3301 [SDOI2013]方程

P3301 [SDOI2013]方程 题意&#xff1a; 题解&#xff1a; 插板法介绍 首先要先讲组合数学的一个方法&#xff1a;插板法 问题引出&#xff1a;把10个球放进三个盒子&#xff0c;每个箱子至少一个有多少种分法&#xff1f; 10个球就有9个空隙&#xff0c;我们可以考虑在这个…

.NET Framework 4.8发布

原文地址&#xff1a;https://devblogs.microsoft.com/dotnet/announcing-the-net-framework-4-8/我们很高兴地宣布今天发布.NET Framework 4.8。它包含在Windows 10 May 2019更新中。.NET Framework 4.8也可在Windows 7和Windows Server 2008 R2 上使用。您可以从我们的 .NET下…

[NewLife.XCode]数据层缓存(网站性能翻10倍)

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;支持nfx/netcore&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xff0c;代…

[NewLife.XCode]高级查询(化繁为简、分页提升性能)

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;支持nfx/netcore&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xff0c;代…

微软云Azure训练营 | 八城联动,全球盛会

Global Azure Bootcamp是由微软发起、MVP参与组织的全球化学习交流活动。每年会挑选一个特定的时间&#xff0c;在同一天内&#xff0c;全球不同地区将同时开展。2019年全球Azure训练营&#xff08;Global Azure Bootcamp&#xff09;将于2019年4月27日在全球270多个城市同时举…

长沙4月21日开发者大会暨.NET社区成立大会活动纪实

活动总结2019年4月21日是一个斜风细雨、微风和煦的美好日子&#xff0c;由长沙.NET技术社区、腾讯云云加社区、微软Azure云技术社区、中国.NET技术社区、长沙柳枝行动、长沙互联网活动基地&#xff08;唐胡子俱乐部&#xff09;等多家单位共同主办的长沙开发者技术大会暨长沙.N…

SQL Server AlwaysOn 集群 关于主Server IP与Listener IP调换的详细测试

1. 背景SQL Server 搭建AlwaysOn后&#xff0c;我们就希望程序连接时使用虚拟的侦听IP&#xff08;Listener IP&#xff09;&#xff0c;而不再是主Server 的IP。如果我们有采用中间件&#xff0c;则可以在配置中&#xff0c;直接用Listener IP 替换掉 Server IP&#xff0c;可…

在 DotNetCore 3.0 程序中使用通用协议方式启动文件关联应用

问题描述在传统的基于 .NET Framework 的 WPF 程序中&#xff0c;我们可以使用如下代码段启动相关的默认应用&#xff1a;Copy# 启动默认文本编辑器打开 helloworld.txtProcess.Start("helloworld.txt");# 启动默认浏览器打开 https:Process.Start("https://hip…