c# - Polly CircuitBreakerAsync 没有按我的预期工作
问题描述
我只是在试用 Polly CircuitBreakerAsync,但它并没有像我预期的那样工作。
我在这里做错了什么?我希望下面的代码完成并说电路仍然关闭。
using Polly;
using System;
using System.Threading.Tasks;
public class Program
{
public static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
var circuitBreaker = Policy
.Handle<Exception>()
.CircuitBreakerAsync(
3, // ConsecutiveExceptionsAllowedBeforeBreaking,
TimeSpan.FromSeconds(5) // DurationOfBreak
);
Console.WriteLine("Circuit state before execution: " + circuitBreaker.CircuitState);
await circuitBreaker.ExecuteAsync(() => Task.Delay(25));
await circuitBreaker.ExecuteAsync(() => Task.Delay(25));
await circuitBreaker.ExecuteAsync(() => { throw new System.Exception(); });
await circuitBreaker.ExecuteAsync(() => Task.Delay(25));
await circuitBreaker.ExecuteAsync(() => Task.Delay(25));
Console.WriteLine("Circuit state after execution: " + circuitBreaker.CircuitState);
}
}
小提琴:https ://dotnetfiddle.net/unfKsC
输出:
Circuit state before execution: Closed
Run-time exception (line 25): Exception of type 'System.Exception' was thrown.
Stack Trace:
[System.Exception: Exception of type 'System.Exception' was thrown.]
at Program.<MainAsync>b__2() :line 25
at Polly.Policy.<>c__DisplayClass116_0.<ExecuteAsync>b__0(Context ctx, CancellationToken ct)
at Polly.CircuitBreakerSyntaxAsync.<>c__DisplayClass4_1.<<CircuitBreakerAsync>b__2>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Polly.CircuitBreaker.CircuitBreakerEngine.<ImplementationAsync>d__1`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Polly.Policy.<ExecuteAsync>d__135.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Program.<MainAsync>d__a.MoveNext() :line 25
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Program.Main(String[] args) :line 9
解决方案
一般断路器
您的代码按预期工作。断路器本身不会中断,因为您已将连续错误计数设置为 3。这意味着如果您有 3 次连续失败的调用,那么它将从Closed
state 转换为Open
. 如果您尝试执行另一个调用,那么它将抛出一个BrokenCircuitException
. 在Closed
状态下,如果已引发异常且未达到阈值,则它会重新引发异常。
我总是建议将断路器视为代理。如果一切正常,它允许调用。如果消耗的子系统/子组件似乎出现故障,那么它将阻止进一步调用以避免不必要的负载。
用于调试的回调函数
当您定义断路器策略时,您可以指定 3 个回调:
onBreak
: 当它从Closed
或转换HalfOpen
到Open
onReset
: 当它从 过渡HalfOpen
到Close
onHalfOpen
: 当它从 过渡Open
到HalfOpen
修改后的政策声明:
var circuitBreaker = Policy
.Handle<Exception>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(5),
onBreak: (ex, @break) => Console.WriteLine($"{"Break",-10}{@break,-10:ss\\.fff}: {ex.GetType().Name}"),
onReset: () => Console.WriteLine($"{"Reset",-10}"),
onHalfOpen: () => Console.WriteLine($"{"HalfOpen",-10}")
);
连续失败计数
让我们将连续失败阈值更改为 1,然后将ExecuteAsync
调用包装在 try catch 中:
var circuitBreaker = Policy
.Handle<Exception>()
.CircuitBreakerAsync(1, TimeSpan.FromSeconds(5),
onBreak: (ex, @break) => Console.WriteLine($"{"Break",-10}{@break,-10:ss\\.fff}: {ex.GetType().Name}"),
onReset: () => Console.WriteLine($"{"Reset",-10}"),
onHalfOpen: () => Console.WriteLine($"{"HalfOpen",-10}")
);
Console.WriteLine("Circuit state before execution: " + circuitBreaker.CircuitState);
try
{
await circuitBreaker.ExecuteAsync(() => Task.Delay(25));
await circuitBreaker.ExecuteAsync(() => Task.Delay(25));
await circuitBreaker.ExecuteAsync(() => { throw new System.Exception(); });
await circuitBreaker.ExecuteAsync(() => Task.Delay(25));
await circuitBreaker.ExecuteAsync(() => Task.Delay(25));
}
catch (Exception ex)
{
Console.WriteLine("Circuit state after execution: " + circuitBreaker.CircuitState);
Console.WriteLine(ex.GetType().Name);
}
Console.WriteLine("Circuit state after execution: " + circuitBreaker.CircuitState);
输出将如下:
Circuit state before execution: Closed
Break 05.000 : Exception
Circuit state after execution: Open
Exception
如您所见,断路器已断开并从状态Closed
变为Open
状态。它重新抛出了你的异常。
结合重试和断路器
为了轻松演示 CB 何时抛出BrokenCircuitException
,我将在 CB 周围使用重试逻辑。
var retry = Policy
.Handle<Exception>()
.Or<BrokenCircuitException>()
.WaitAndRetryAsync(
retryCount: 1,
sleepDurationProvider: _ => TimeSpan.FromSeconds(1),
onRetry: (exception, delay, context) =>
{
Console.WriteLine($"{"Retry",-10}{delay,-10:ss\\.fff}: {exception.GetType().Name}");
});
Exception
此策略将尝试在抛出a 或抛出 a时重新执行您的委托BrokenCircuitException
。它在初始尝试和第一次(也是唯一一次)重试之间有 1 秒的延迟。
让我们结合这两个策略并修改ExecuteAsync
调用:
var strategy = Policy.WrapAsync(retry, circuitBreaker);
try
{
await strategy.ExecuteAsync(() => { throw new System.Exception(); });
}
catch (Exception ex)
{
Console.WriteLine("Circuit state after execution: " + circuitBreaker.CircuitState);
Console.WriteLine(ex.GetType().Name);
}
输出将如下:
Circuit state before execution: Closed
Break 05.000 : Exception
Retry 01.000 : Exception
Circuit state after execution: Open
BrokenCircuitException
- 初始调用失败并抛出一个
Exception
- CB 中断,因为已达到阈值并重新抛出异常
- 组合策略会将问题从 CB 升级到 Retry
- 重试处理
Exception
这就是为什么它在尝试再次重新执行委托之前等待一秒钟 - 重试尝试再次调用委托,但失败,因为 CB就是抛出
Open
a 的原因BrokenCircuitException
- 因为没有进一步的重试,所以重试策略将重新抛出其异常(现在是一个
BrokenCircuitException
实例) - 该异常被我们的
catch
块捕获。
微调示例
让我们稍微修改一下这些策略的参数:
- CB的
durationOfBreak
从5秒到1.5 - 重试
retryCount
从 1 到 2
var retry = Policy
.Handle<Exception>()
.Or<BrokenCircuitException>()
.WaitAndRetryAsync(2, _ => TimeSpan.FromSeconds(1),
onRetry: (exception, delay, context) =>
{
Console.WriteLine($"{"Retry",-10}{delay,-10:ss\\.fff}: {exception.GetType().Name}");
});
var circuitBreaker = Policy
.Handle<Exception>()
.CircuitBreakerAsync(1, TimeSpan.FromMilliseconds(1500),
onBreak: (ex, @break) => Console.WriteLine($"{"Break",-10}{@break,-10:ss\\.fff}: {ex.GetType().Name}"),
onReset: () => Console.WriteLine($"{"Reset",-10}"),
onHalfOpen: () => Console.WriteLine($"{"HalfOpen",-10}")
);
Console.WriteLine("Circuit state before execution: " + circuitBreaker.CircuitState);
var strategy = Policy.WrapAsync(retry, circuitBreaker);
try
{
await strategy.ExecuteAsync(() => { throw new System.Exception(); });
}
catch (Exception ex)
{
Console.WriteLine("Circuit state after execution: " + circuitBreaker.CircuitState);
Console.WriteLine(ex.GetType().Name);
}
输出将如下:
Circuit state before execution: Closed
Break 01.500 : Exception
Retry 01.000 : Exception
Retry 01.000 : BrokenCircuitException
HalfOpen
Break 01.500 : Exception
Circuit state after execution: Open
Exception
我希望这个小演示应用程序可以帮助您更好地了解断路器的工作原理。
推荐阅读
- google-cloud-run - Google Cloud Run 和 golang 协程
- r - 根据高于某个值的每一行从一个数据帧创建多个数据帧
- typescript - 为了将 main 与测试编译分开,需要设置哪些正确的 typescript 编译器 (tsc) 选项?
- javascript - 使用 lambda 发送 html 文件会产生语法错误,但只是有时
- gitlab - Merge request dropped from merge train with "No stages / jobs for this pipeline."
- java - Java:在 arraylist 初始化程序中构造类会导致错误
- c++ - 需要一个可调整大小的容器用于 std::mutex
- windows-7 - (AvaloniaUI) 在 Windows 7 中使用 OpenFileDialog 时引发 ArgumentException
- swift - 如何在 switch 语句中解开双选项——Swift
- python - 如何打印字符串的最后一次出现加上以下 N 行