多个 HTTP 请求触发 HTTP Client 超时

Posted

技术标签:

【中文标题】多个 HTTP 请求触发 HTTP Client 超时【英文标题】:Multiple HTTP requests trigger HTTP Client timeout 【发布时间】:2020-02-01 03:39:51 【问题描述】:

我有一个异步发送 500 个 HTTP 请求的应用。 15 秒后处理的所有请求都因 HTTP 客户端超时而失败,即使请求的端点已经返回 200 OK。

代码非常简单。在这里,我们获取一大块请求(500),并异步执行它们。需要注意的是,下面的函数是运行在基于消费的计划上的 Azure 函数。

    public async Task RunBatch(List<Request> requests)
    
        if (requests != null && requests .Count > 0)
        
            var tasks = new Task[requests.Count];
            var i = 0;
            foreach (var request in requests)
            
                var request = new HttpRequestMessage(HttpMethod.Post, new Uri(request.Url));
                request.Content = new StringContent(request.BodyString, Encoding.UTF8, "application/json");
                tasks[i] = _httpClient.SendAsync(request);
                i++;
            

            await Task.WhenAll(tasks);
        
    

以下代码存在于我的构造函数中

_httpClient = new HttpClient();
_httpClient.Timeout = new TimeSpan(0, 0, 15); // 15 seconds

这是来自 Azure 的日志。

我希望每个请求的超时时间为 15 秒。但是,每当我的服务器开始处理等待的请求时,我需要它给我一个准确的响应代码。这可能吗?

我应该注意:使用更高的 Http 超时(1 分钟),所有请求都会成功。

【问题讨论】:

500 个并发请求对于单个客户端来说是很多的。您需要非常小心地处理线程,以确保您的异步任务可以获得足够的 CPU 时间以避免触发超时。 可以分享代码吗?看起来您正在循环中调用异步。 添加了代码。在这种情况下,为 20 个或类似的批次使用单独的 HTTP 客户端是否有意义?这需要打开很多端口,所以我猜不是。 【参考方案1】:

就我个人而言,我认为尝试发出 500 个并发请求总是容易出错。您提到您正在异步执行此操作,但实际上,当您启动 500 个“热”任务然后等待它们全部完成时,您的代码中并没有太多异步。

我会使用信号量来控制一次可以发出多少个请求。您可能需要玩弄数字才能找到最佳位置。

以下代码在 LINQPad 中运行良好(尽管 bing 很快注意到奇数个请求并开始向页面添加验证码):

// using System.Threading;
async Task Main()

    var httpClient = new HttpClient();
    var urls = Enumerable.Range(1, 500).Select(e => "https://www.bing.com/").ToList();
    
    // 10 concurrent requests - tweak this number
    var semaphore = new SemaphoreSlim(10, 10);
    
    var tasks = urls.Select(u => MakeRequest(u, semaphore, httpClient));
        
    var allResponses = await Task.WhenAll(tasks);
    
    // Do something with allResponses


private async Task<string> MakeRequest(string url, SemaphoreSlim semaphore, HttpClient httpClient)

    try
    
        await semaphore.WaitAsync();
        var request = new HttpRequestMessage(HttpMethod.Get, new Uri(url));
        var response = await httpClient.SendAsync(request);
        
        // Add an optional delay for further throttling:
        //await Task.Delay(TimeSpan.FromMilliseconds(100));
        
        return await response.Content.ReadAsStringAsync();
    
    finally
    
        semaphore.Release();
    

【讨论】:

保罗 - 感谢您的回复。我以前从未听说过 SemaphoreSlim。虽然这可能不是我想要的,因为我真的试图一次执行尽可能多的 HTTP 请求。我的应用程序允许稍后请求 HTTP 请求,因此在高负载期间,可能会有数千个请求发出。我目前正在通过启动处理 500 个批次的无服务器 Azure Functions 来解决这个问题。也许我唯一的选择是减少每批次处理的数量。 我怀疑这是您想要的,因为它应用了您需要的节流级别。当快速收到响应时,会快速发出下一个请求;但是,如果没有迅速收到响应,它就不会继续增加新的请求。这样,您的请求总数就会尽快得到处理。看起来很适合我! 我同意 Paul 的观点,这很合适。我刚刚使用 SemaphoreSlim 解决了一个类似的任务。就我而言,我想处理 200 个订单(处理包括对 DB 和第三方 API 的多个请求)。最初在我的测试中,处理在 2 分钟后因相同的超时而失败,限制其运行时间为 15 秒,没有任何问题。【参考方案2】:

HttpClient 有几个问题。

例如:

    HttpClient 是一次性的:使用带有 using 语句的 HttpClient 不是最佳选择,因为即使您处理 HttpClient 对象,底层套接字也不会立即释放,并可能导致称为“套接字耗尽”的严重问题。有关此问题的更多信息,You're using httpclient wrong and it is destabilizing your software

    HttpClient 旨在被实例化一次并在应用程序的整个生命周期中重复使用。为每个请求实例化一个 HttpClient 类将耗尽重负载下可用的套接字数量。该问题将导致 SocketException 错误。解决该问题的可能方法是基于将 HttpClient 对象创建为单例或静态。

    当您将其用作单例或静态对象时可以拥有的 HttpClient。在这种情况下,单例或静态 HttpClient 不考虑 DNS 更改。欲了解更多信息:Singleton HttpClient doesn't respect DNS changes

为了解决上述问题并简化 HttpClient 实例的管理,.NET Core 2.1 引入了新的 HttpClientFactory

什么是HttpClientFactory:

    为命名和配置逻辑 HttpClient 对象提供一个中心位置。例如,您可以配置一个预先配置为访问特定微服务的客户端(服务代理)。 通过在 HttpClient 中委派处理程序并实施基于 Polly 的中间件以利用 Polly 的弹性策略,对传出中间件的概念进行编码。 HttpClient 已经有了委派处理程序的概念,这些处理程序可以为传出的 HTTP 请求链接在一起。您将 HTTP 客户端注册到工厂,然后您可以使用 Polly 处理程序将 Polly 策略用于 Retry、CircuitBreakers 等。 管理 HttpClientMessageHandlers 的生命周期以避免在您自己管理 HttpClient 生命周期时可能出现的上述问题/问题。

更多详情请访问此链接:Use HttpClientFactory to implement resilient HTTP requests

您也可以使用 RestSharp 库来发送任何请求。更多http://restsharp.org/

参考资源:

    https://josefottosson.se/you-are-probably-still-using-httpclient-wrong-and-it-is-destabilizing-your-software/ https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests https://github.com/dotnet/runtime/issues/18348

【讨论】:

【参考方案3】:

我要检查的几件事:

与其每次都使用一个新的HttpClient,不如使用HttpClientFactory来避免HttpClient构造的开销和端口的耗尽。

您确定您没有受到 Bing 的速率限制吗?尝试以您控制的远程服务器为目标。

考虑使用SemaphoreSlim 或并行库来限制请求的并发性。

【讨论】:

【参考方案4】:

尝试增加默认连接限制:

ServicePointManager.UseNagleAlgorithm = true;
ServicePointManager.Expect100Continue = true;
ServicePointManager.DefaultConnectionLimit = <number>;

【讨论】:

我用第二张图片更新了我的问题,它更清楚地显示了问题的时间。在多次运行中,没有一致的失败次数。我不认为这与连接数有关。【参考方案5】:

您似乎正在向 bing 搜索引擎发送请求,对吧?如果我是对的,你还有另一个问题。必应搜索可能不允许机器人请求。因此,必应阻止请求。请进一步检查。您可以尝试一些时间延迟而不是异步请求。您的应用程序似乎工作正常。您可以通过向另一个服务发送请求来验证我的理论或 API。

【讨论】:

感谢您的回复。我应该注意到,如果我将 HTTP Timeout 增加到 1 分钟以上,那么所有请求都会成功。 @BrandonMcAlees - 1 分钟不会使这个答案无效。也许 bing 可以使用这个设置。您是否尝试过自己拥有的服务器?

以上是关于多个 HTTP 请求触发 HTTP Client 超时的主要内容,如果未能解决你的问题,请参考以下文章

Java异步执行多个HTTP请求的例子(需要apache http类库)

如何使用 HTTP 请求触发 Firebase 函数以从多个节点读取数据?

HTTP协议学习笔记---HTTP持久连接和如何正确地关闭HTTP连接

如何在http get请求中设置标头?

go 1秒钟可以发多少http请求

Netty的http client连接池设计