我正在使用polly DecorrelatedJitterBackoff策略重试http请求。我的用例类似于当timeSpan达到300秒时,它应该重试int。每300秒的最大次数。
我正在尝试使用以下代码来实现这一点。我使用了int.MaxValue,它给出了超出范围的异常,所以我使用210000000。代码可以工作,但执行起来花费了太多时间。请提出实现这一目标的有效方法?
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
参数的定义,那么你可以看到它是一个函数,它将根据一些输入(如当前重试计数、上下文等(产生时间跨度
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)
( - 在重试策略用完初始回退后,我们将始终返回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
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
与ExecuteAsync
与ExecuteCapture
与
根据您指定策略的方式,您可以调用Execute
或ExecuteAsync
。前者用于同步操作,后者用于异步I/O操作。
RetryPolicy<HttpResponseMessage> retryPolicy = Policy.....WaitAndRetryForever(...
retryPolicy.Execute(() => ....);
或
AsyncRetryPolicy<HttpResponseMessage> retryPolicy = Policy.....WaitAndRetryForeverAsync(...
await retryPolicy.ExecuteAsync(async () => await ...)
ExecuteAsync
预计会有一个异步函数(Func<Task<...>>
(,这就是为什么我们必须使用async() => await ...
- CCD_ 26确实返回了CCD_ 27,因此也应该等待它
如果RequestServer
是一个同步方法,则使用前者;如果它是异步的,则使用后者。
如果您仅对结果本身的策略相关信息不感兴趣,我还鼓励您使用简单的Execute
而不是ExecuteAndCapture
。
var result = retryPolicy.Execute(() => this.RequestServer(equipmentId));
sleepDurations.Dispose();