【问题标题】:Maximum number of retries using DecorrelatedJitterBackoff使用 DecorrelatedJitterBackoff 的最大重试次数
【发布时间】:2021-02-15 18:54:14
【问题描述】:

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

我正在尝试使用以下代码来实现这一点。我使用了 int.MaxValue,它给出了超出范围的异常,所以我使用的是 2100000000。代码有效,但执行时间过长。请提出一种有效的方法来实现这一点?

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

提前致谢

【问题讨论】:

    标签: c# polly


    【解决方案1】:

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

    sleepDurationProvider

    如果您查看WaitAndRetry 方法的sleepDurationProvider 参数的定义,您会发现它是一个函数,它会根据当前重试次数、上下文等某些输入生成时间跨度。

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

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

    这是一个示例方法,它将按需生成 TimeSpan:

    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 =&gt; time &lt; fiveMinutes))
    • 重试策略用尽初始回退后,我们将始终返回 5 分钟。
    • 请注意,此迭代器永远不会返回。
      • 但因为它是按需消费的,所以它不是一个大问题。

    让我们通过查询前 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
    

    WaitAndRetryWaitAndRetryForever

    尽管前者确实有几个接受IEnumerable&lt;TimeSpan&gt; 参数的重载,但我不推荐它。大多数重载都需要一个显式的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&lt;TimeSpan&gt; 的任何重载,这就是我们必须使用一些样板代码的原因。
    • sleepDurations 是一个迭代器,每次重试策略需要计算睡眠持续时间时,我们都会向前推进。
    • 此算法不考虑当前重试次数 (retry),因此您可以根据需要使用丢弃的次数 (.WaitAndRetryForever(_ =&gt; ...)

    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&lt;Task&lt;...&gt;&gt;),这就是我们必须使用 async() =&gt; await ... 的原因
    • ExecuteAsync 确实返回 Task,因此也应该等待它。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-30
      • 2021-02-03
      相关资源
      最近更新 更多