提问人:camelCase 提问时间:9/30/2023 最后编辑:Peter CsalacamelCase 更新时间:10/2/2023 访问量:247
Polly V8 断路器行为不同,电路卡断开
Polly V8 CircuitBreaker behavior is different, the circuit gets stuck open
问:
我曾尝试升级到 Polly 的 V8,但现在断路器的状态无限期地卡在打开状态。
在 Nuget 下载 V8 Polly 包后,我的经典版本继续工作,但后来我尝试采用实质性的 V8 api 更改,包括 、 和 。现在我遇到了卡住的开路问题。AdvancedCircuitBreakerAsync
ResiliencePipelineBuilder
CircuitBreakerStrategyOptions
CircuitBreakerStateProvider
V8 问题仅在我使用以下逻辑跳过重复调用时发生:_resiliencePipeline.ExecuteAsync(...)
if (_circuitBreakerStateProvider.CircuitState == CircuitState.Open)
{
// return fallback value indicating remote service is unavailable but
// the CircuitState never progresses to HalfOpen
return null;
}
await _resiliencePipeline.ExecuteAsync( ... );
经典断路器的等效逻辑按预期工作:
if (_breakerPolicy.CircuitState == CircuitState.Open)
{
return null;
}
// after the configured durationOfBreak timespan expires we get here with circuit at HalfOpen
await _breakerPolicy.ExecuteAsync( ... );
如果不进一步调用,电路状态似乎不会从 Open 进展到 HalfOpen_resiliencePipeline.ExecuteAsync()
更新 #1
应 @Peter Csala的要求。
这是我的管道配置,它是为低流量情况下的快速故障要求而设计的。没有针对 的直接 DI 配置。管道在应用程序类中声明并保存,该应用程序类本身是单一实例,断点确认管道已生成一次,并且所有测试都是通过 Blazor 服务器应用程序的单用户手动 UI 测试进行的。ResiliencePipeline
RpcFacade
services.AddSingleton<RpcFacade>();
_breakerState = new CircuitBreakerStateProvider();
var builder = new ResiliencePipelineBuilder<List<LiveAgentInfo>>().AddCircuitBreaker( new CircuitBreakerStrategyOptions<List<LiveAgentInfo>>
{
FailureRatio = 0.1,
SamplingDuration = TimeSpan.FromSeconds( 10 ),
MinimumThroughput = 2,
BreakDuration = TimeSpan.FromSeconds( 15 ),
ShouldHandle = new PredicateBuilder<List<LiveAgentInfo>>().Handle<RpcException>(),
StateProvider = _breakerState
} );
更新 #2
我启用了额外的 Polly 日志记录。这将记录一个条目“发生弹性事件。EventName: 'OnCircuitOpened'“,因为在第二次失败的 GRPC 调用到无法访问的远程 GRPC 服务时,Circuit 从”关闭“转换为”打开“。
在确认 Closed 到 Open 转换之前进行日志记录,以便实例保持对 CircuitBreaker 内部状态的有效观察。_circuitBreakerStateProvider.CircuitState
_resiliencePipeline.ExecuteAsync( ... )
_circuitBreakerStateProvider
更新 #3
进一步的测试揭示了另一个见解。在上面的原始代码中,我展示了如何在 == 时返回回退值,以避免在打开 15 秒窗口期间调用。CircuitState
CircuitState.Open
_resiliencePipeline.ExecuteAsync(...)
如果即使电路状态为打开,我也总是打电话,那么我会在打开的窗口期间得到,然后在 15 秒后,断路器通过远程呼叫,触发 .此时,我看到一个日志条目“发生弹性事件。EventName: 'OnCircuitHalfOpened' 在日志中“。_resiliencePipeline.ExecuteAsync( ... )
BrokenCircuitExceptions
RpcException
断路器似乎只识别出它在 期间已达到状态条件。然后,它进行在我的 lambda 中表示的外部 grpc 调用,该调用失败,状态返回为 Open。从外部角度来看,状态似乎停留在打开状态。HalfOpen
resiliencePipeline.ExecuteAsync( ... )
CircuitBreakerStateProvider
作为一种解决方法,我可以在捕获中返回一个回退值,这失败得很快,是我想要的结果。BrokenCircuitException
答:
TL;DR:V7 的属性吸气剂比 V8 的更复杂。CircuitState
为了理解为什么 V7 会这样做,以及为什么 V8 不会在不调用的情况下从状态过渡到状态,我们需要了解一下引擎盖下的情况。我保证不会很痛苦。Open
HalfOpen
ExecuteAsync
在这两种情况下,我们都有一个控制器类,它是有状态的,可以执行繁重的工作。策略/策略使用控制器来请求状态转换。
V7版本
在这里,我们有一个带有一堆字段的 CircuitStateController
。
从这个问题的角度来看,重要的一点是:
protected readonly TimeSpan _durationOfBreak;
protected long _blockedTill;
protected CircuitState _circuitState;
...
protected readonly Action _onHalfOpen;
- 捕获一个日期时间,直到 CB 必须保持状态才能过渡到
_blockedTill
Open
HalfOpen
- 当 CB 中断时,使用 SystemClock 和
_durationOfBreak
- 当 CB 中断时,使用 SystemClock 和
- 仅当评估
_onHalfOpen
CircuitState
public CircuitState CircuitState
{
get
{
if (_circuitState != CircuitState.Open)
{
return _circuitState;
}
using (TimedLock.Lock(_lock))
{
if (_circuitState == CircuitState.Open && !IsInAutomatedBreak_NeedsLock)
{
_circuitState = CircuitState.HalfOpen;
_onHalfOpen();
}
return _circuitState;
}
}
}
为什么从问题的角度来看这很重要?因为在您提前退出的情况下,您直接要求断路器重新评估电路状态()。这就是它从 ._breakerPolicy.CircuitState == CircuitState.Open
Open
HalfOpen
这也意味着 V7 不会自动从 到 转换。如果您的 CB 中断并且您没有直接或通过 评估,它将保持状态(并且您的 onHalfOpen
不会被触发)。Open
HalfOpen
CircuitState
ExecuteAsync
Open
V8发动机
在新版本中,仅从以下方法调用用户委托:_onHalfOpen
ScheduleHalfOpenTask
private Task ScheduleHalfOpenTask(ResilienceContext context)
{
_executor.ScheduleTask(() => _onHalfOpen!(new OnCircuitHalfOpenedArguments(context)).AsTask(), context, out var task);
return task;
}
并且此方法仅从 .
以下是该方法的摘录:OnActionPreExecuteAsync
public async ValueTask<Outcome<T>?> OnActionPreExecuteAsync(ResilienceContext context)
{
...
lock (_lock)
{
// check if circuit can be half-opened
if (_circuitState == CircuitState.Open && PermitHalfOpenCircuitTest_NeedsLock())
{
_circuitState = CircuitState.HalfOpen;
_telemetry.Report(new(ResilienceEventSeverity.Warning, CircuitBreakerConstants.OnHalfOpenEvent), context, new OnCircuitHalfOpenedArguments(context));
isHalfOpen = true;
}
exception = ...
if (isHalfOpen && _onHalfOpen is not null)
{
task = ScheduleHalfOpenTask(context);
}
}
...
}
正如您已经发现的那样,每当执行策略时,策略才会调用它。OnActionPreExecuteAsync
ExecuteCore
每当检索电路状态时,都不会执行太多操作:CircuitBreakerStateProvider
public CircuitState CircuitState => _circuitStateProvider?.Invoke() ?? CircuitState.Closed;
调用的方法在调用中指定(来自策略)。Initialize
stateProvider?.Initialize(() => _controller.CircuitState);
控制器属性的 getter 非常简单。CircuitState
public CircuitState CircuitState
{
get
{
EnsureNotDisposed();
lock (_lock)
{
return _circuitState;
}
}
}
如您所见,它不会执行任何检查是否应该过渡到。HalfOpen
总结
- 在 V7 的情况下,cb 策略从 转换为,因为您访问 和 getter 可能会更改断路器的状态。
Open
HalfOpen
CircuitState
- 在 V8 的情况下,cb 策略不会自动转换,因为状态提供程序只是返回控制器的当前状态,并且不会引起任何状态更改。
Open
HalfOpen
我希望这并不痛苦,并且描述澄清了某些事情。:)
评论