首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >揭开HTTP超时的神秘面纱,用Polly重试

揭开HTTP超时的神秘面纱,用Polly重试
EN

Stack Overflow用户
提问于 2022-09-13 01:43:25
回答 1查看 142关注 0票数 1

登记服务:

代码语言:javascript
复制
var host = new HostBuilder().ConfigureServices(services =>
{
    services.AddHttpClient<Downloader>(client =>
    {
        client.Timeout = TimeSpan.FromSeconds(1); // -- T1
    })
    .AddPolicyHandler(HttpPolicyExtensions
        .HandleTransientHttpError()
        .Or<HttpRequestException>()
        .WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(
            TimeSpan.FromSeconds(5), // -- T2
            retryCount: 3)))
    .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(10)) // -- T3
    .AddPolicyHandler(HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))); // -- T4

    services.AddTransient<Downloader>();

}).Build();

Downloader的实现

代码语言:javascript
复制
class Downloader
{
    private HttpClient _client;
    public Downloader(IHttpClientFactory factory)
    {
        _client = factory.CreateClient();
    }

    public void Download(List<Uri> links)
    {
        await Parallel.ForEachAsync(
            links, 
            async (link, _cancelationToken) =>
            {
                await _client.GetStreamAsync(uri, _cancellationToken);
            });
    }
}

在这个伪代码中,我对超时与如何/何时重新提交HTTP请求之间的相关性感到困惑。具体地说:

  • T1T2T3T4是如何“编排”的?我假设如果端点在T1中没有响应,await _client.GetStreamAsync抛出超时异常,那么在与T2相关的时间间隔内,HTTP请求将提交最大的3时间,或者如果断路器计时器到达T4。那么T3的作用是什么呢?
  • 是否所有配置都与客户端和HttpMessageHandler相关,我仍然需要按以下方式包装对GetStreamAsync的调用?!
代码语言:javascript
复制
Policy
    .Handle<Exception>()
    .RetryAsync(3)
    .ExecuteAsync(
        async () => await _client.GetStreamAsync(uri, _cancellationToken));
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-09-13 09:32:52

首先让我提供一些建议,然后讨论你的问题。

更喜欢Wrap而不是多个AddPolicyHandler

AddPolicyHandler注册一个PolicyHttpMessageHandler,这是一个DelegatingHandler。在您的例子中,您有3个DelegatingHandler,所以异常传播是由ASP.NET核心完成的,而不是Polly。

如果您希望使用Policy.WrapAsync,那么您可以以Polly方式(升级)链接策略。

代码语言:javascript
复制
var T2 = HttpPolicyExtensions
        .HandleTransientHttpError()
        .Or<HttpRequestException>()
        .WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(
            TimeSpan.FromSeconds(5),
            retryCount: 3));

var T3 = Policy.TimeoutAsync<HttpResponseMessage>(10);

var T4 = HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
代码语言:javascript
复制
var resilienceStrategy = Policy.WrapAsync<HttpResponseMessage>(T2, T3, T4);
var host = new HostBuilder().ConfigureServices(services =>
{
    services
      .AddHttpClient<Downloader>(client =>
        client.Timeout = TimeSpan.FromSeconds(1))
      .AddPolicyHandler(resilienceStrategy);

    services.AddTransient<Downloader>();

}).Build();

进一步建议

交换超时和断路器

顺便说一句,交换超时和CircuitBreaker策略可能是有意义的。如果超时将是最内在的,您将调整断路器策略,以了解超时问题(.Or<TimeoutRejectedException>()),那么它也可能中断。

代码语言:javascript
复制
var T3 = Policy.TimeoutAsync<HttpResponseMessage>(10);

var T4 = HttpPolicyExtensions
        .HandleTransientHttpError()
        .Or<TimeoutRejectedException>()
        .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));

var resilienceStrategy = Policy.WrapAsync<HttpResponseMessage>(T2, T4, T3);

在更多情况下重试

在超时或中断电路的情况下执行重试也可能是有意义的。

代码语言:javascript
复制
var T2 = HttpPolicyExtensions
        .HandleTransientHttpError()
        .Or<HttpRequestException>()
        .Or<TimeoutRejectedException>()
        .Or<BrokenCircuitException>()
        .WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(
            TimeSpan.FromSeconds(5),
            retryCount: 3));

对问题2的答复

请允许我从你的第二个问题开始。

是否所有配置都与客户端和HttpMessageHandler相关,我仍然需要按以下方式包装对GetStreamAsync的调用?!

不,你不需要那样做。由于您已经用您的弹性策略来修饰您的HttpClient,这就是为什么您不需要对每个HttpClient的方法调用执行同样的操作。

对问题1的答复

请允许我将此分为多个问题。

T1、T2、T3和T4是如何“编排”的?

  • T1 (HttpClient's Timeout)执行全局超时操作。这意味着如果您需要执行几次重试,则在1秒后它们将被取消。因此,对于所有尝试,这都是一个总体的时间限制。。

  • T2 (Retry's backoff's medianFirstRetryDelay)以指数退避的方式提供了两次重试之间的睡眠持续时间序列和抖动。换句话说,与其每次尝试之间总是等待相同的时间,不如每一次等待的时间越来越多。

  • T3 (超时值)执行本地超时。换句话说,这个超时是为每次重试尝试而休息和强制执行的。
    • 这是HttpClientTimeout的对比,它是一个全局超时。
    • 如果将超时策略定义为最外部策略(Policy.WrapAsync的最左边参数),那么它也是全局的。

  • T4 (巡回断路器的breakDuration)充当一个看门人。如果CB中断(在您的情况下连续5次失败之后),那么它将自己转换为Open状态。
    • 当CB打开时,任何请求都用BrokenCircuitException终止
    • 经过breakDuration之后,它将转换为HalfOpen状态,并允许一个探测。如果成功,CB将移回关闭状态,否则为Open

我假设如果端点在T1中没有响应,等待_client.GetStreamAsync抛出异常,然后在与T2相关的时间间隔内,将提交最多3次HTTP请求,或者如果断路器计时器到达T4。那么T3的作用是什么呢?

关于前一点,我想我已经解决了这个问题:)。因为您已经将全局超时设置为1秒,这就是为什么本地超时(10秒)永远不会触发的原因。

如果要为全局超时设置更高的值,则可能触发每个请求(基于重试尝试)超时。

如果上面的任何一点不清楚,请让我知道,我将链接我之前的一些帖子,其中详细讨论了这一点。

更新#1

请您详细介绍一下T1对T3的情况好吗?

在以下这样的主题中,我试图说明全局超时和本地超时之间的区别:

最后是下面是一个SO主题,介绍如何比HttpClient的超时时间更长

我是否应该实现捕获CB的开放、HalfOpen和关闭状态以及缓冲和保存请求w.r.t的逻辑。状态,或CB内部缓冲区,并在适当时重新提交请求?

断路器不是那样工作的。电路断路器不维护类似请求队列的东西。它只是一个代理,如果下游系统被视为暂时不可用,它可以缩短请求的执行。CB本身没有执行任何重试逻辑。

限幅器政策也以同样的方式工作。在有足够的吞吐量之前,它不会保存请求。

您可以做的是创建一个CB感知的重试逻辑,并将它们组合起来。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73696754

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档