如果HTTP调用失败,我在非常基本的情况下使用Polly进行指数退回:
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>();
}
我有一个仅在while(true)
中创建HttpListener
并循环的测试API。目前,我正在尝试测试客户在每个呼叫中接收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不是问题。
在 HttpClient
中使用polly 使用效果不佳。单个SendAsync
旨在单个呼叫。即:
- 任何
HttpClient
超时都将适用于单个SendAsync
调用。 -
HttpClient
的某些版本也会处理其内容,因此不能在下一个SendAsync
呼叫中重复使用。 - 如评论中指出的这种悬挂是已知的问题,不能是由Polly确定。
底线:覆盖SendAsync
非常适合添加前请求和重新收回逻辑。这不是重试的正确位置。
相反,请使用常规HttpClient
,并使您的Polly Logic Retry exife GetStringAsync
(或其他(调用。
这似乎是.NET框架已知问题并在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
}));