繁体   English   中英

Polly 重试并不总是捕获 HttpRequestException

[英]Polly retry not always catching HttpRequestException

我的 .NET Core 3.1 应用程序使用 Polly 7.1.0 重试和隔板策略来实现 http 弹性。 重试策略使用HandleTransientHttpError()来捕获可能的HttpRequestException

现在,使用MyClient触发的 http 请求有时会返回HttpRequestException 其中大约一半被波莉抓住并重试。 然而,另一半最终出现在我的try-catch块中,我必须手动重试它们。 这发生最大重试次数用尽之前。

我是如何设法创建一个竞争条件来阻止 Polly 捕获所有异常的? 我该如何解决这个问题?

我使用IHttpClientFactory注册策略如下。

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<MyClient>(c =>
    {
        c.BaseAddress = new Uri("https://my.base.url.com/");
        c.Timeout = TimeSpan.FromHours(5); // Generous timeout to accomodate for retries
    })
        .AddPolicyHandler(GetHttpResiliencePolicy());
}

private static AsyncPolicyWrap<HttpResponseMessage> GetHttpResiliencePolicy()
{
    var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(1), retryCount: 5);

    var retryPolicy = HttpPolicyExtensions
            .HandleTransientHttpError() // This should catch HttpRequestException
            .OrResult(msg => msg.StatusCode == HttpStatusCode.NotFound)
            .WaitAndRetryAsync(
                sleepDurations: delay,
                onRetry: (response, delay, retryCount, context) => LogRetry(response, retryCount, context));

    var throttlePolicy = Policy.BulkheadAsync<HttpResponseMessage>(maxParallelization: 50, maxQueuingActions: int.MaxValue);

    return Policy.WrapAsync(retryPolicy, throttlePolicy);
}

触发 http 请求的MyClient如下所示。

public async Task<TOut> PostAsync<TOut>(Uri requestUri, string jsonString)
{
    try
    {
        using (var content = new StringContent(jsonString, Encoding.UTF8, "application/json"))
        using (var response = await httpClient.PostAsync(requestUri, content)) // This throws HttpRequestException
        {
            // Handle response
        }
    }
    catch (HttpRequestException ex)
    {
        // This should never be hit, but unfortunately is
    }
}

这是一些附加信息,尽管我不确定它是否相关。

  1. 由于HttpClientDI 临时注册的,因此每个工作单元有 10 个实例飞来飞去。
  2. 每个工作单元,客户端会触发约 400 个 http 请求。
  3. http 请求很长(5 分钟持续时间,30 MB 响应)

重试和HttpRequestException

每当我们谈论 Polly 策略时,我们可以区分两种不同的例外情况:

  • 处理
  • 未处理。

处理异常

  • 它触发给定策略的某种行为(在本例中为HttpRequestException )。
  • 如果策略无法成功,则再次抛出已处理的异常。
  • 如果有其他策略,那么它可能会或可能不会处理该异常。

未处理的异常

  • 它不会引起任何类型的反应(例如我们的例子中的WebException )。
  • 未处理的异常流经策略。
  • 如果有其他策略,那么它可能会或可能不会处理该异常。

“其中大约一半被波莉抓住并重试。
然而,另一半最终出现在我的 try-catch-block 中”

如果您的某些重试次数用完,就会发生这种情况。 换句话说,有些请求在 6 次尝试(5 次重试和 1 次初始尝试)中无法成功。

这可以使用以下两种工具之一轻松验证:

  • onRetry + context
  • Fallback + context

onRetry + context

onRetry在触发重试策略时但在睡眠持续时间之前被调用。 委托接收retryCount 因此,为了能够连接/关联同一请求的单独日志条目,您需要使用某种相关 ID。 拥有一个的最简单方法可以这样编码:

public static class ContextExtensions
{
    private const string Key = "CorrelationId";

    public static Context SetCorrelation(this Context context, Guid? id = null)
    {
        context[Key] = id ?? Guid.NewGuid();
        return context;
    }

    public static Guid? GetCorrelation(this Context context)
    {
        if (!context.TryGetValue(Key, out var id))
            return null;

        if (id is Guid correlation)
            return correlation;

        return null;
    }
}

这是一个简化的示例:
要执行的方法

private async Task<string> Test() 
{ 
    await Task.Delay(1000); 
    throw new CustomException(""); 
}

政策

var retryPolicy = Policy<string>
    .Handle<CustomException>()
    .WaitAndRetryAsync(5, _ => TimeSpan.FromSeconds(1),
        (result, delay, retryCount, context) =>
        {
            var id = context.GetCorrelation();
            Console.WriteLine($"{id} - #{retryCount} retry.");
        });

用途

var context = new Context().SetCorrelation();
try
{
    await retryPolicy.ExecuteAsync(async (ctx) => await Test(), context);
}
catch (CustomException)
{
    Console.WriteLine($"{context.GetCorrelation()} - All retry has been failed.");
}

样品 output

3319cf18-5e31-40e0-8faf-1fba0517f80d - #1 retry.
3319cf18-5e31-40e0-8faf-1fba0517f80d - #2 retry.
3319cf18-5e31-40e0-8faf-1fba0517f80d - #3 retry.
3319cf18-5e31-40e0-8faf-1fba0517f80d - #4 retry.
3319cf18-5e31-40e0-8faf-1fba0517f80d - #5 retry.
3319cf18-5e31-40e0-8faf-1fba0517f80d - All retry has been failed.

Fallback

正如人们所说,只要策略无法成功,它将重新抛出已处理的异常。 换句话说,如果策略失败,那么它将问题升级到下一个级别(下一个外部策略)。

这是一个简化的示例:
政策

var fallbackPolicy = Policy<string>
    .Handle<CustomException>()
    .FallbackAsync(async (result, ctx, ct) =>
    {
        await Task.FromException<CustomException>(result.Exception);
        return result.Result; //it will never be executed << just to compile
    }, 
    (result, ctx) =>
    {
        Console.WriteLine($"{ctx.GetCorrelation()} - All retry has been failed.");
        return Task.CompletedTask;
    });

用途

var context = new Context().SetCorrelation();
try
{
    var strategy = Policy.WrapAsync(fallbackPolicy, retryPolicy);  
    await strategy.ExecuteAsync(async (ctx) => await Test(), context);
}
catch (CustomException)
{
    Console.WriteLine($"{context.GetCorrelation()} - All policies failed.");
}

样品 output

169a270e-acf7-45fd-8036-9bd1c034c5d6 - #1 retry.
169a270e-acf7-45fd-8036-9bd1c034c5d6 - #2 retry.
169a270e-acf7-45fd-8036-9bd1c034c5d6 - #3 retry.
169a270e-acf7-45fd-8036-9bd1c034c5d6 - #4 retry.
169a270e-acf7-45fd-8036-9bd1c034c5d6 - #5 retry.
169a270e-acf7-45fd-8036-9bd1c034c5d6 - All retry has been failed.
169a270e-acf7-45fd-8036-9bd1c034c5d6 - All policies failed.

暂无
暂无

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

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