如何使用 dotnet core 和 Polly 添加动态重试策略

How to add dynamic retry policies using dotnet core and Polly

我有一个 dotnet 核心 (2.1) 控制台应用程序,我正在使用 Polly 用重试策略包装我的一段代码。这适用于如下所示的简单用例:

private void ProcessRun() 
{
    var policy = Policy.Handle<SocketException>().WaitAndRetryAsync(
                 retryCount: 3
                 sleepDurationProvider: attempt => TimeSpan.FromSeconds(10),
                 onRetry: (exception, calculatedWaitDuration) => 
                 {
                    Console.WriteLine($"Retry policy executed for type SocketException");
                 });

    try
    {
        CancellationTokenSource _cts = new CancellationTokenSource()

        PollyRetryWaitPolicy.ExecuteAsync(async token => {
           MyOperation(token);
        }, _cts.Token)
        .ContinueWith(p => {
           if (p.IsFaulted || p.Status == TaskStatus.Canceled)
           {
                Console.WriteLine("faulted or was cancelled");
           }
        })
        .ConfigureAwait(false);
    }
    catch (Exception ex) {
     Console.WriteLine($"Exception has occurred: {ex.Message}");
    }
}

然后我使用此代码对其进行测试:

private void MyOperation() 
{
    Thread.Sleep(2000);
    throw new SocketException();
}

代码执行时,如期捕获了socket异常。

我一直在寻找一种灵活的方式来用多个策略而不是一个来包装我的代码。我更改了代码以动态添加一些包装的 Polly 重试策略,以允许捕获不止一种错误类型并轻松更改我正在寻找的异常。我将代码更改为:

internal PolicyWrap PollyRetryWaitPolicy;

public void AddRetryWaitPolicy<T>(int waitTimeInSeconds, int retryAttempts)
    where T: Exception
{

    // Setup the polly policy that will be added to the executing code.
    var policy = Policy.Handle<T>().WaitAndRetryAsync(
                retryCount: retryAttempts, 
                sleepDurationProvider: attempt => TimeSpan.FromSeconds(waitTimeInSeconds), 
                onRetry: (exception, calculatedWaitDuration) => 
                {
                    Console.WriteLine($"Retry policy executed for type {typeof(T).Name}");
                });

    if (host.PollyRetryWaitPolicy == null)
    {
        // NOTE: Only add this timeout policy as it seems to need at least one
        // policy before it can wrap! (suppose that makes sense).
        var timeoutPolicy = Policy
            .TimeoutAsync(TimeSpan.FromSeconds(waitTimeInSeconds), TimeoutStrategy.Pessimistic);
        PollyRetryWaitPolicy = policy.WrapAsync(timeoutPolicy);
    }
    else
    {
        PollyRetryWaitPolicy.WrapAsync(policy);
    }
}

private void ProcessRun() 
{
    AddRetryWaitPolicy<SocketException>(10, 5);
    AddRetryWaitPolicy<InvalidOperationException>(5, 2);

    try
    {
        Console.WriteLine($"Calling HostedProcess.Run() method. AwaitResult: {awaitResult}");

        CancellationTokenSource _cts = new CancellationTokenSource()

        PollyRetryWaitPolicy.ExecuteAsync(async token => {
           MyOperation(token);
        }, _cts.Token)
        .ContinueWith(p => {
           if (p.IsFaulted || p.Status == TaskStatus.Canceled)
           {
                Console.WriteLine("Process has faulted or was cancelled");
           }
        })
        .ConfigureAwait(false);
    }
    catch (Exception ex) {
     Console.WriteLine($"Exception has occurred: {ex.Message}");
    }
}

当我使用这段代码进行测试时,上面的代码按预期工作并且重试了 5 次。

private void MyOperation() 
{
    Thread.Sleep(2000);
    throw new SocketException();
}

但是当我尝试以下操作时,它没有按预期重试 2 次(根本没有重试):

private void MyOperation() 
{
    Thread.Sleep(2000);
    throw new InvalidOperationException();
}

我做错了什么?以上所有的要点是根据我的需要动态地制定多个政策。有没有比 WrapPolicy 更好的方法?

多谢指点!

这里:

if (host.PollyRetryWaitPolicy == null)
{
    // NOTE: Only add this timeout policy as it seems to need at least one
    // policy before it can wrap! (suppose that makes sense).
    var timeoutPolicy = Policy
        .TimeoutAsync(TimeSpan.FromSeconds(waitTimeInSeconds), TimeoutStrategy.Pessimistic);
    PollyRetryWaitPolicy = policy.WrapAsync(timeoutPolicy);
}
else
{
    PollyRetryWaitPolicy.WrapAsync(policy);
}

看来 if 分支和 else 分支采用不一致的方法来对重试策略和包装中的超时策略进行排序。

  • if分支中,超时策略包含在重试策略中。所以超时充当每次尝试超时。
  • else 分支中,任何现有的重试和超时策略都包装在新重试策略之外。所以超时策略 新的重试策略之外。
    • 新的重试策略设置为waitTimeInSeconds后重试;但是超时策略也设置为在waitTimeInSeconds之后执行超时。因此,一旦第一次重试(对于第二次或后续配置的重试策略)发生,超时就会中止整个执行。因此,正如您观察到的那样,重试永远不会发生。

要解决这个问题,您可以将 else 分支更改为:

PollyRetryWaitPolicy = policy.WrapAsync(PollyRetryWaitPolicy);

背景:见PolicyWrap wiki recommendations on ordering policies in a PolicyWrap。根据您将 TimeoutPolicy 放在 RetryPolicy 内部还是外部,TimeoutPolicy 充当(在内部时)每次尝试超时,或(在外部时)作为所有尝试的整体超时。