Polly CircuitBreakerAsync가 예상대로 작동하지 않습니다.

Nov 17 2020

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 00110001 Nov 17 2020 at 05:53

예상대로 작동합니다.

https://github.com/App-vNext/Polly/wiki/Circuit-Breaker

예외 처리

회로 차단기는 측정 및 차단 장치로 존재합니다.이를 통해 수행 한 작업에서 발생한 처리 된 예외를 측정하고 구성된 실패 임계 값을 초과 할 때 차단합니다.

  • 회로 차단기는 재 시도를 조정하지 않습니다.
  • 회로 차단기는 재시 도와 달리 예외를 흡수하지 않습니다. 정책을 통해 실행 된 작업에 의해 throw되는 모든 예외 (정책에 의해 처리되고 처리되지 않은 예외 모두)는 의도적으로 다시 throw 됩니다. 회로 상태를 관리하는 정책 업데이트 메트릭에 의해 처리되는 예외 정책에서 처리하지 않는 예외는 그렇지 않습니다.

요컨대 예외를 처리하지 않고 다시 발생시킵니다.

4 PeterCsala Nov 17 2020 at 15:47

일반적으로 회로 차단기

코드가 예상대로 작동합니다. 연속 오류 수를 3으로 설정했기 때문에 회로 차단기 자체는 중단되지 않습니다. 이는 3 번의 연속 실패 호출이있는 경우 Closed상태에서로 전환됨을 의미 합니다 Open. 또 다른 호출을 실행하려고하면 BrokenCircuitException. 에서는 Closed예외가 발생되어 상기 임계 값에 도달하지 않은 경우, 다음 상태는 예외가 발생-재.

나는 항상 Circuit Breaker를 프록시로 고려할 것을 제안합니다. 모든 것이 잘 작동하면 호출을 허용합니다. 소비 된 하위 시스템 / 하위 구성 요소가 오작동하는 것처럼 보이면 불필요한로드를 피하기 위해 추가 호출을 방지합니다.

디버깅을위한 콜백 함수

회로 차단기 정책을 정의 할 때 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

당신이 볼 수 있듯이 회로 차단기가 고장으로부터 이동했습니다 ClosedOpen상태. 예외가 다시 발생했습니다.

재시도 및 회로 차단기 결합

CB가 언제 던지는 지 쉽게 보여주기 위해 CB BrokenCircuitException주변에 재시도 로직을 사용합니다.

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이나 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
  1. 초기 호출이 실패하고 Exception
  2. 임계 값에 도달하여 CB가 중단되고 예외가 다시 발생합니다.
  3. 결합 된 정책은 CB에서 재시 도로 문제를 에스컬레이션합니다.
  4. 재시도 핸들 Exception은 대리자를 다시 실행하기 전에 잠시 대기하는 이유입니다.
  5. 재 시도는 대리인을 다시 호출하려고 시도하지만 CB가 Open그 때문에 a BrokenCircuitException가 던져 지기 때문에 실패합니다.
  6. 더 이상 재 시도가 없기 때문에 재시도 정책이 예외 (이제 BrokenCircuitException인스턴스 임)를 다시 throw합니다.
  7. 그 예외는 우리 catch블록에 의해 포착됩니다 .

미세 조정 된 예

이러한 정책의 매개 변수를 약간 수정 해 보겠습니다.

  • CB durationOfBreak는 5 초에서 1.5로
  • 재시도 retryCount1 ~ 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

이 작은 데모 앱이 Circuit Breaker가 어떻게 작동하는지 더 잘 이해하는 데 도움이 되었기를 바랍니다.