Polly WaitAndRetryAsync 在一次重试后卡住了。

4

我在非常基本的场景中使用 Polly 做指数退避(exponential backoff),以便在 HTTP 调用失败时重新尝试:

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    return await HandleTransientHttpError()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(4, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)))
        .ExecuteAsync(async () => await base.SendAsync(request, cancellationToken).ConfigureAwait(false));
}

private static PolicyBuilder<HttpResponseMessage> HandleTransientHttpError()
{
    return Policy
        .HandleResult<HttpResponseMessage>(response => (int)response.StatusCode >= 500 || response.StatusCode == System.Net.HttpStatusCode.RequestTimeout)
        .Or<HttpRequestException>();
}

我有一个测试API,它只创建了一个HttpListener并在一个while(true)循环中运行。目前,我正在尝试测试客户端在每次调用时接收到500时是否能正确重试。

while (true)
{
    listener.Start();
    Console.WriteLine("Listening...");
    HttpListenerContext context = listener.GetContext();
    HttpListenerRequest request = context.Request;

    HttpListenerResponse response = context.Response;
    response.StatusCode = (int)HttpStatusCode.InternalServerError;

    //Thread.Sleep(1000 * 1);
    string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
    byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
    response.ContentLength64 = buffer.Length;
    System.IO.Stream output = response.OutputStream;
    output.Write(buffer, 0, buffer.Length);
    output.Close();
    listener.Stop();
}

如果以上的代码全部正确,重试会在3、9、27和81秒后发生。

然而,如果我取消注释Thread.Sleep 的调用,客户端只重试一次,然后挂起直到其他3次重试超时,这不是正确的行为。

实际的生产API也会出现相同的情况,这让我相信这不是我的测试API的问题。


尝试使用ConfigureAwait(false)来等待HandleTransientHttpError() - Theodor Zoulias
@Theodor Zoulias:不幸的是,那并没有解决问题。我执行这个操作的上下文类似于控制台应用程序,因此从理论上讲,我认为这不会有任何区别。 - Tudor
这是在.NET Framework 4.6.1上吗?它看起来像是这个问题的一个例子:https://github.com/App-vNext/Polly/issues/642。那个问题不是由Polly引起的 - 请参见https://github.com/App-vNext/Polly/issues/642和https://github.com/App-vNext/Polly/issues/658上的讨论。 - mountain traveller
@山行者:确实是在.NET Framework 4.6.1上。 - Tudor
@Tundor:你有解决它的方法吗? - AstroBoy
@AstroBoy:是的,我按照Stephen Cleary的建议,将Polly重试逻辑移出了实际的http调用。 - Tudor
2个回答

5

HttpClient中使用Polly并不是很好。单个SendAsync调用是被设计用来进行一次调用的,例如:

  • 任何HttpClient超时都会应用于单个SendAsync调用。
  • 某些版本的HttpClient也会销毁其内容,因此无法在下一个SendAsync调用中重复使用。
  • 正如注释中所述,这种挂起是已知的问题,并且不能通过Polly解决。

底线是:覆盖SendAsync非常适合添加预请求和后请求逻辑,但它不是重试的正确位置。

相反,使用常规的HttpClient,并让您的Polly逻辑在GetStringAsync(或其他)调用之外重试。


您对在 RetryAsync 回调中处理结果的解决方法有何想法? - scuba88
1
@scuba88:我还没有尝试过。只要它不关闭底层套接字,它可能会运行良好。 - Stephen Cleary

0

这似乎是解决.NET Framework和在HttpClient中使用Polly的已知问题的适当解决方法。我们必须在重试时处理结果,以允许多个请求。请参见原始问题此处上的讨论以及描述解决方法此处的另一个讨论。我只是简单地测试了一下它是否有效,但没有完全研究可能存在的副作用。

Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(msg => RetryableStatusCodesPredicate(msg.StatusCode))
.RetryAsync(retryCount, onRetry: (x, i) =>
{
    x.Result.Dispose(); // workaround for https://github.com/aspnet/Extensions/issues/1700
}));

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