使用 DecorrelatedJitterBackoff 的最大重试次数

Maximum number of retries using DecorrelatedJitterBackoff

我正在使用 polly DecorrelatedJitterBackoff 策略重试 http 请求。我的用例是当 timeSpan 达到 300 秒时,它应该每 300 秒重试 int.maximum 次。

我正在尝试使用以下代码实现此目的。我使用了 int.MaxValue,它给出了超出范围的异常,所以我使用的是 2100000000。代码有效,但花费太多时间 execute.Please 建议一种有效的方法来实现这个?

    private static readonly List<int> ExceptionCodes = new List<int> { 408, 429, 500, 503, 504, 520 };
    var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(2500), 10);

    var decorrelatedJitterDelay = this.GetTimeSpanList(delay.ToArray());

    this.RetryPolicy = Policy.HandleResult<HttpResponseMessage>(r => ExceptionCodes.Contains((int)r.StatusCode))
     .WaitAndRetryAsync(decorrelatedJitterDelay);

     var policyResult = this.RetryPolicy.ExecuteAndCaptureAsync(() => this.RequestServer(equipmentId));



     private IEnumerable<TimeSpan> GetTimeSpanList(TimeSpan[] delay)
            {
                var index = 0;
                var timeSpanList = new List<TimeSpan>();
                foreach (var time in delay)
                {
                    if (time > TimeSpan.FromSeconds(300))
                    {
                       var timeDelay = TimeSpan.FromSeconds(300);
                       delay[index] = timeDelay;
                       timeSpanList.Add(delay[index]);
                    }

                    index++;
                }

               // 2100000000  is the maximum capacity of List<>.
                for (int i = index; i < 2100000000 - index; i++)
                {
                    timeSpanList.Add(TimeSpan.FromSeconds(300));
                }

                return timeSpanList;
            }

提前致谢

为了实现所需的行为,需要修改一些小的东西。

sleepDurationProvider

如果你查看 WaitAndRetry 方法的 sleepDurationProvider 参数的定义,你会发现它是一个函数,它会根据当前重试次数、上下文等输入生成 timeSpan等..

Func<int, TimeSpan>
Func<int, DelegateResult<HttpResponseMessage>, Context, TimeSpan>
...

因此,我们可以按需计算它们,而不是提前指定每个睡眠持续时间。这真的很好,因为我们可以利用 yield return 通过考虑以前的时间跨度来按需创建新的 TimeSpan。

下面是一个示例方法,它将按需生成 TimeSpans:

private static IEnumerable<TimeSpan> GetDelay()
{
    TimeSpan fiveMinutes = TimeSpan.FromMinutes(5);
    var initialBackOff =  Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(2500), 10);
    foreach (var delay in initialBackOff.Where(time => time < fiveMinutes))
    {
        yield return delay;
    }

    while (true)
    {
        yield return fiveMinutes;
    }
}
  • 我们使用 DecorrelatedJitterBackoffV2.
  • 生成前 10 个持续时间
  • 我们过滤掉那些超过 5 分钟的 (Where(time => time < fiveMinutes))
  • 重试策略耗尽初始退避后,我们将始终 return 5 分钟。
  • 请注意,此迭代器从不 returns。
    • 但是因为它是按需消费的,所以这不是一个大问题。

让我们通过查询前 20 个睡眠持续时间来测试此方法:

foreach (var ts in GetDelay().Take(20))
{
    Console.WriteLine(ts.TotalSeconds);
}

输出将是:

0.5985231
4.0582524
5.1969925
15.4724158
16.4869722
15.8198397
75.7497326
118.5080045
272.2401684
300
300
300
300
300
300
300
300
300
300
300

WaitAndRetry 对比 WaitAndRetryForever

尽管前者确实有几个接受 IEnumerable<TimeSpan> 参数的重载,但我不推荐它。大多数重载都需要一个明确的 retryCount,这就是为什么在大多数人看来这个函数被认为是一个预定义的、有限的重试执行器。

我建议使用 WaitAndRetryForever 因为它表达了意图。无需查看睡眠持续时间生成器,很明显我们想要什么。

这里是精炼的RetryPolicy定义:

var sleepDurations = GetDelay().GetEnumerator();
var retryPolicy = Policy
    .HandleResult<HttpResponseMessage>(r => ExceptionCodes.Contains((int)r.StatusCode))
    .WaitAndRetryForever(retry =>
    {
        sleepDurations.MoveNext();
        return sleepDurations.Current;
    });
  • WaitAndRetryForever 没有任何接受 IEnumerable<TimeSpan> 的重载,这就是为什么我们必须使用一些样板代码。
  • sleepDurations 是一个迭代器,每次重试策略需要计算睡眠持续时间时,我们都会向前移动。
  • 此算法不考虑当前的重试次数 (retry),因此如果您愿意,可以在那里使用丢弃 (.WaitAndRetryForever(_ => ...)

Execute vs ExecuteAsync vs ExecuteCapture vs ...

根据您指定策略的方式,您可以调用 ExecuteExecuteAsync。前者用于同步操作,后者用于异步 I/O 操作。

RetryPolicy<HttpResponseMessage> retryPolicy = Policy.....WaitAndRetryForever(...
retryPolicy.Execute(() => ....);

AsyncRetryPolicy<HttpResponseMessage> retryPolicy = Policy.....WaitAndRetryForeverAsync(...
await retryPolicy.ExecuteAsync(async () => await ...)
  • ExecuteAsync 预期一个异步函数 (Func<Task<...>>) 这就是为什么我们必须使用 async() => await ...
  • ExecuteAsyncTask 做 return 所以也应该等待它。

如果您的 RequestServer 是同步方法,则使用前者,如果是异步,则使用后者。

如果您对结果本身的政策相关信息不感兴趣,我也鼓励您使用简单的 Execute 而不是 ExecuteAndCapture

var result = retryPolicy.Execute(() => this.RequestServer(equipmentId));
sleepDurations.Dispose();