Polly的CircuitBreakerAsync在发生异常时不会重试

7

我正在使用Polly库来处理瞬态故障。对于同步操作,Polly断路器策略运行良好,但当我创建其异步版本时,它不会重试执行。请建议:

异步方法:

private async static Task HelloWorld()
    {
        if (DateTime.Now < programStartTime.AddSeconds(10))
        {
            Console.WriteLine("Task Failed.");
            throw new TimeoutException();
        }
        await Task.Delay(TimeSpan.FromSeconds(1));
        Console.WriteLine("Task Completed.");
    }

Polly断路器异步策略:

private static void AsyncDemo3(Func<Task> action)
    {
        programStartTime = DateTime.Now;

        AsyncPolicy policy = Policy
            .Handle<TimeoutException>()
            .CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
        try
        {
            var a = policy.ExecuteAndCaptureAsync(action, true).GetAwaiter().GetResult();
        }
        catch (AggregateException ex)
        {
            Console.WriteLine("Exception: " + ex.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: " + ex.Message);
        }
    }

执行Polly断路器策略:

AsyncDemo3(HelloWorld);

请帮助找到并解决问题。
3个回答

16

我认为您误解了断路器策略的作用。

它的作用是,如果调用了给定次数并且每次都失败,那么它将在一定时间内停止调用给定方法。但它本身不会重试。

因此,要实现您想要的功能,您需要将重试策略与断路器策略相结合。其中一种方法是:

AsyncPolicy retryPolicy = Policy.Handle<TimeoutException>().RetryAsync(3);

AsyncPolicy circuitBreakerPolicy = Policy
    .Handle<TimeoutException>()
    .CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));

try
{
    retryPolicy.ExecuteAsync(() => circuitBreakerPolicy.ExecuteAsync(action, true))
        .GetAwaiter().GetResult();
}
…

这段代码的输出结果是:

Task Failed.
Task Failed.
Task Failed.
Exception: The circuit is now open and is not allowing calls.

4
Polly现在还添加了PolicyWrap功能,可以使组合策略的语法更加简洁:retryPolicy.WrapAsync(circuitBreakerPolicy).ExecuteAsync(...) - mountain traveller

2

建议使用PolicyWrap创建并组合策略,具体操作如下。

创建策略

 var circuitBreakerPolicy = Policy
        .Handle<TimeoutException>()
        .CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
 var retryPolicy = Policy.Handle<TimeoutException>().RetryAsync(3);

 // Combined policy: outermost first, innermost last
 var policy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);

使用策略

 await this.policy.ExecuteAsync(async () => await SomeFooMethodAsync(cancellationToken));

1

svick'suser3613932's的回答只展示了整个故事的一部分。也就是说,如何执行多次操作以打开断路器。那么接下来我们该怎么办呢?

在这篇文章中,让我尝试回答这个问题。

断路器的一般情况

您应该将CB视为代理。如果下游系统被视为健康,则允许每个请求通过。如果CB通过检查后续响应(如果有的话)检测到瞬态故障,则会通过抛出异常来捷径新的请求。

这里出现了重要的部分:它仅阻止新请求在特定的时间内。如果经过了一段时间,它允许一个请求作为探针到达下游系统:

  • 如果失败,则再次为给定时间段内的所有新请求提供快捷方式
  • 如果成功,则允许所有新请求到达下游系统

换句话说,断路器是一种模式,用于避免向下游系统(被认为是超载或暂时不可用)洪水般的请求,直到它再次变得健康/可用。

修改建议的重试

让我们保留您的断路器定义,因此在连续3个TimeoutException之后,它应该拒绝所有新请求2秒钟。

上述两位SO成员都建议使用此重试策略:

Policy.Handle<TimeoutException>().RetryAsync(3);

3 次重试意味着尝试 4 次(1 次初始和 3 次重试)。因为 CB 被设置为在连续 3 次 TimeoutException 后打开,所以在第四次尝试时 CB 将抛出一个 BrokenCircuitException

因为没有策略应触发 BrokenCircuitException,所以 ExecuteAsync 将该异常抛给调用者。

让我们修改重试策略,使其也触发此异常,并将 retryCount 从 3 修改为 6。

Policy.Handle<TimeoutException>().Or<BrokenCircuitException>().RetryAsync(6);

现在策略也会触发BrokenCircuitException,但是CB会快捷地跳过我们4次重试中的3次。为什么?因为CB会开放2秒钟并拒绝所有重试尝试。(一个尝试只需要几毫秒就可以被拒绝)。

因此,更好的解决方案是在每次尝试之间等待而不是立即重试。

Policy.Handle<TimeoutException>()
      .Or<BrokenCircuitException>()
      .WaitAndRetryAsync(6, _ => TimeSpan.FromSeconds(1));

通过这种方法,我们会比CB的中断持续时间更长地重试。换句话说,现在我们的重试尝试之一是探针,它可能会成功。这就是引入弹性逻辑以克服瞬态故障的总体目标。

进一步推动基本思想

我已经在StackOverflow上发布了很多有关断路器的示例代码。
让我在这里与您分享最相关的内容:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接