繁体   English   中英

HttpClient Polly WaitAndRetry 策略

[英]HttpClient Polly WaitAndRetry policy

任何人都知道为什么下面的政策在 3 次而不是 10 次后停止重试?

IAsyncPolicy<HttpResponseMessage> httpWaitAndRetryPolicy =
     Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
          .OrHandle<Exception>(r => true)
          .WaitAndRetryAsync(10, retryAttempt => TimeSpan.FromSeconds(2));

我将重试尝试设置为 10 并测试 http 后调用,但 BadRequest 失败。 但它只重试 3 次然后停止直到超时并抛出异常

  ----> System.Threading.Tasks.TaskCanceledException : A task was canceled.
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at HttpRetry.Lab.Tests.ServiceTest.WhenPostWrongAlert_ThenRecoversProperly() in C:\ServiceTest.cs:line 56
--TaskCanceledException


15:57:03.6367  INFO HttpClientProvider - Configuring client xxxxxxxx:1234/api/" timeout=00:02:00
15:57:03.6636  INFO Service            - POST xxxx/xxxxxxx
15:57:04.2051  INFO HttpClientProvider - Retrying retryCount=1 sleepDuration=00:00:02 result=Polly.DelegateResult`1[System.Net.Http.HttpResponseMessage]
15:57:06.6880  INFO HttpClientProvider - Retrying retryCount=2 sleepDuration=00:00:02 result=Polly.DelegateResult`1[System.Net.Http.HttpResponseMessage]
15:59:03.6811  INFO HttpClientProvider - Retrying retryCount=3 sleepDuration=00:00:02 result=Polly.DelegateResult`1[System.Net.Http.HttpResponseMessage]
15:59:03.6811 ERROR ServiceTest - System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at HttpRetry.Lab.Service.<PostAsync>d__4.MoveNext() in C:\Service.cs:line 38
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at HttpRetry.Lab.Tests.ServiceTest.<PostAsync>d__4.MoveNext() in C:\ServiceTest.cs:line 27

var serviceProvider = serviceConnection.AddHttpClient(connection.Name, c =>
        {
            c.BaseAddress = new Uri(connection.BaseUrl);
            c.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{connection.UserName}:{connection.Password}")));
            c.Timeout = connection.Timeout; // Timeout is TimeSpan.FromSeconds(120)
        })
    .AddPolicyHandler(httpWaitAndRetryPolicy)
    .Services.BuildServiceProvider();

HttpClientFactories.Add(connection.Name, serviceProvider.GetService<IHttpClientFactory>());

确认问题的根本原因:我不知道是什么导致了症状,但看起来请求连接不会被释放,除非显式调用 Dispose HttpResponseMessage OnRetry。 当前的解决方案是在 WaitAndRetryAsync 中设置 OnRetry 并处理响应。 一切正常,无需更改 ServicePointManager.DefaultConnectionLimit

你的超时

正如我所看到的,您在 HttpClient 级别上有 1 分钟的全局超时。 即使您可能期望TimeoutException ,这也会引发TaskCanceledException

如果您希望接收TimeoutException ,则必须通过HttpRequestMessageRequestTimeout属性指定基于请求的超时。 有关详细信息,请查看以下链接

您的重试

您的重试逻辑定义了 3 次(或 10 次)重试,惩罚为 5 秒。 3 次重试意味着 4 次尝试,因为有一个初始(第 0 个)请求,它在重试 scope 之外。 如果失败,那么第一次重试将成为第二次尝试。

所以流程将如下所示:

  1. 发出初始请求 << 第一次尝试
  2. 初始请求失败
  3. 触发重试逻辑
  4. 5秒处罚暴露
  5. 第一次重试逻辑触发 << 第二次尝试
  6. 第二次尝试失败
  7. 触发重试逻辑
  8. 5秒处罚暴露
  9. 第二次重试逻辑触发 << 第三次尝试
  10. ...

如果所有这些都可以在一秒钟内完成,那么由于全局超时,HttpClient 将抛出一个TaskCanceledExpcetion

Polly 的超时策略

Polly 也支持本地和全局超时策略。 有一个单一的超时策略,可以以两种方式使用。

如果您的超时策略包含在重试中,它可以充当本地超时: retryPolicy.WrapAsync(timeoutPolicy);

如果您的重试策略包含在超时内,它可以充当全局超时: timeoutPolicy.WrapAsync(retryPolicy);

当然,您也可以同时拥有全局和本地超时: Policy.WrapAsync(globalTimeoutPolicy, retryPolicy, localTimeoutPolicy);

我强烈建议您考虑使用 Polly 的 Timeout 而不是 HttpClient 的 Timeout,以便在一个地方定义您的弹性策略。

请记住,如果超时没有响应,超时策略将抛出TimeoutRejectedException 因为您的重试处理各种异常( .OrHandle<Exception>() ),所以您不需要修改重试策略。

Polly 的瞬时故障的错误处理

有一个名为Microsoft.Extensions.Http.Polly (1 ) 的 nuget package,它定义了几个有用的实用程序。 其中之一是HttpPolicyExtensions.HandleTransientHttpError()

它捕获HttpRequestException并检查响应的状态代码是5xx还是408 (RequestTimeout)。

也可能值得考虑使用它。

调试策略

每个不同的策略都定义了回调,以提供了解它们如何工作的能力。 在重试的情况下,分别调用onRetryonRetryAsync进行同步或异步重试。 通过在WaitAndRetryAsync中提供以下委托,您可以获得非常有用的信息:

onRetryAsync: (exception, delay, times, context) => {
  //TODO: logging
}

结果,我们的团队发现 Polly Retry 策略在使用 HttpResponseMessage 之前不会释放 http 请求连接。 确切地说,这与 Polly Retry 无关,只是连接不会被释放,直到原始 HttpClient.SendAsync 得到返回。 并且由于 WaitAndRetry 而发生的重试策略有点延迟。 结束 Polly 重试策略(例如 x 次)可能最终会使用每个 BadRequest 的 x+1 个并发 http 连接。

有两种“使用”HttpResponseMessage 的方法。 通过显式调用 response.Result.Dispose 或对响应内容执行某种读取。 例如 response.Result.ReadAsAsync。 好吧,另一种方法是等待 httpClient 超时,但我相信这并不是我们真正想要的。 这是使事情正常进行的代码。 关键是 OnRetry 中的 HttpResponse.Dispose

    ServicePointManager.DefaultConnectionLimit = appConfiguration.HttpClients.ConnectionLimit;

    IAsyncPolicy<HttpResponseMessage> httpWaitAndRetryPolicy =
        Policy.Handle<HttpRequestException>()
              .Or<Exception>()
              .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
              .WaitAndRetryAsync(3, 
                                 retryAttempt => TimeSpan.FromSeconds(appConfiguration.HttpClients.RetryFactor * retryAttempt), 
                                 OnRetry);

    IAsyncPolicy<HttpResponseMessage> timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(appConfiguration.HttpClients.RequestTimeout);

    foreach (var endpoint in appConfiguration.HttpClients.HttpEndpoints)
    {
        var serviceProvider = serviceConnection.AddHttpClient(endpoint.Name,
                              c =>
                              {
                                  c.BaseAddress = new Uri(endpoint.BaseUrl);
                                  c.DefaultRequestHeaders.Authorization   = new System.Net.Http.Headers.AuthenticationHeaderValue(endpoint.AuthenticationScheme, Convert.ToBase64String(Encoding.ASCII.GetBytes($"{endpoint.UserName}:{endpoint.Password}")));
                                  c.DefaultRequestHeaders.ConnectionClose = false;
                                  c.Timeout     = endpoint.Timeout;
                              }).AddPolicyHandler(Policy.WrapAsync(httpWaitAndRetryPolicy, timeoutPolicy))
                              .Services.BuildServiceProvider();

        httpClientFactories.Add(endpoint.Name, serviceProvider.GetService<IHttpClientFactory>());
    }

    private Task OnRetry(DelegateResult<HttpResponseMessage> response, TimeSpan span, int retryCount, Context context)
    {
        if (response == null)
            return Task.CompletedTask;

        var responseResult = response.Result;
        logger.Info($"RetryCount={retryCount} IsSuccess={responseResult == null ? "" : responseResult.IsSuccessStatusCode} StatusCode={responseResult == null ? "" : responseResult.StatusCode} Exception={response.Exception?.Message});

        response.Result?.Dispose();
        return Task.CompletedTask;
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM