Polly V8 断路器行为不同,电路卡断开

Polly V8 CircuitBreaker behavior is different, the circuit gets stuck open

提问人:camelCase 提问时间:9/30/2023 最后编辑:Peter CsalacamelCase 更新时间:10/2/2023 访问量:247

问:

我曾尝试升级到 Polly 的 V8,但现在断路器的状态无限期地卡在打开状态。

在 Nuget 下载 V8 Polly 包后,我的经典版本继续工作,但后来我尝试采用实质性的 V8 api 更改,包括 、 和 。现在我遇到了卡住的开路问题。AdvancedCircuitBreakerAsyncResiliencePipelineBuilderCircuitBreakerStrategyOptionsCircuitBreakerStateProvider

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 测试进行的。ResiliencePipelineRpcFacade

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 秒窗口期间调用。CircuitStateCircuitState.Open_resiliencePipeline.ExecuteAsync(...)

如果即使电路状态为打开,我也总是打电话,那么我会在打开的窗口期间得到,然后在 15 秒后,断路器通过远程呼叫,触发 .此时,我看到一个日志条目“发生弹性事件。EventName: 'OnCircuitHalfOpened' 在日志中“_resiliencePipeline.ExecuteAsync( ... )BrokenCircuitExceptionsRpcException

断路器似乎只识别出它在 期间已达到状态条件。然后,它进行在我的 lambda 中表示的外部 grpc 调用,该调用失败,状态返回为 Open。从外部角度来看,状态似乎停留在打开状态。HalfOpenresiliencePipeline.ExecuteAsync( ... )CircuitBreakerStateProvider

作为一种解决方法,我可以在捕获中返回一个回退值,这失败得很快,是我想要的结果。BrokenCircuitException

c# 波莉 断路器

评论

0赞 Peter Csala 9/30/2023
有趣。能否请与我们分享一下熔断政策和策略的定义?

答:

3赞 Peter Csala 10/2/2023 #1

TL;DR:V7 的属性吸气剂比 V8 的更复杂。CircuitState


为了理解为什么 V7 会这样做以及为什么 V8 不会在不调用的情况下从状态过渡到状态,我们需要了解一下引擎盖下的情况。我保证不会很痛苦。OpenHalfOpenExecuteAsync

在这两种情况下,我们都有一个控制器类,它是有状态的,可以执行繁重的工作。策略/策略使用控制器来请求状态转换。

V7版本

在这里,我们有一个带有一堆字段的 CircuitStateController
从这个问题的角度来看,重要的一点是:

protected readonly TimeSpan _durationOfBreak;
protected long _blockedTill;
protected CircuitState _circuitState;
...
protected readonly Action _onHalfOpen;
  • 捕获一个日期时间,直到 CB 必须保持状态才能过渡到_blockedTillOpenHalfOpen
    • 当 CB 中断时,使用 SystemClock 和_durationOfBreak
  • 仅当评估_onHalfOpenCircuitState
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.OpenOpenHalfOpen

这也意味着 V7 不会自动从 到 转换。如果您的 CB 中断并且您没有直接或通过 评估,它将保持状态(并且您的 onHalfOpen 不会被触发)。OpenHalfOpenCircuitStateExecuteAsyncOpen

V8发动机

在新版本中,仅从以下方法调用用户委托:_onHalfOpenScheduleHalfOpenTask

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);
        }
    }

    ...
}

正如您已经发现的那样,每当执行策略时,策略才会调用它。OnActionPreExecuteAsyncExecuteCore


每当检索电路状态时,都不会执行太多操作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 可能会更改断路器的状态。OpenHalfOpenCircuitState
  • 在 V8 的情况下,cb 策略不会自动转换,因为状态提供程序只是返回控制器的当前状态,并且不会引起任何状态更改。OpenHalfOpen

我希望这并不痛苦,并且描述澄清了某些事情。:)