HttpClient 和套接字耗尽 - 澄清?

Posted

技术标签:

【中文标题】HttpClient 和套接字耗尽 - 澄清?【英文标题】:HttpClient and socket exhaustion - clarification? 【发布时间】:2019-10-17 08:05:25 【问题描述】:

This article 表示我们应该使用静态的HttpClient 来重用套接字。

但是那里的第一条评论说存在DNS更改识别问题,解决方案在另一篇文章here

第二条建议:

var client = new HttpClient();
client.DefaultRequestHeaders.ConnectionClose = true; 

哪个控制KeepAlive 标头。 但是会妨碍您利用重用套接字的好处

另一个解决方案是:

var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar/baz/123?a=ab"));
sp.ConnectionLeaseTimeout = 60*1000; // 1 minute

但是:

他没有说我们每次要发出请求时是否应该使用new Httpclient,还是应该仍然使用静态的。

问题:

假设我想使用这个解决方案:

var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar/baz/123?a=ab"));
sp.ConnectionLeaseTimeout = 60*1000; // 1 minute 
我还应该使用static HttpClient 方法吗?还是每次我想打电话时都应该new HttpClient? 或者 - 我应该为每个 scheme://basedns 创建静态/非静态 new HttpClient 吗?

他展示了问题,但他的结论没有展示整个正确的最终解决方案。

请注意 - 我问的是 .net 框架。不是 .net 核心。

【问题讨论】:

那是一篇描述第一个问题的非常老文章。它不包含第二个问题的任何解决方案。该解决方案是使用 HttpClientFactory 代替它保留自己的 HttpClientHandlers 池并定期回收它们。 @PanagiotisKanavos 您在文章中哪里看到了 HttpClientFactory 这个词?我还认为您在谈论.net core(我的问题不是关于core),您在其中注入类型/名称的httpclient-或IhttpClientFactory(确实使用了处理程序池)。我的问题的第二个链接也解释了第二个问题。 我说这是一篇非常老文章。通过引入 HttpClientFactory 在接下来的 3 年里解决了这个问题。 【参考方案1】:

这是一篇非常老文章,它解释了为什么应该重用 HttpClient,但没有解释如何处理 DNS 更改。这在Singleton HttpClient? Beware of this serious behaviour and how to fix it 中有解释。这仍然只处理一个连接。

显而易见的答案是避免使用单例 HttpClient,但仍会在一段时间内重复使用它们。此外,不同的套接字用于连接到不同的服务器,这意味着我们确实需要重用(和回收)每个服务器的套接字。 解决方案后来以 HttpClientFactory 的形式出现。

好消息是 HttpClientFactory 是一个 .NET Standard 2.0 包,Microsoft.Extensions.Http 可供 .NET Core 和 .NET Old 使用,而不仅仅是 ASP.NET Core 应用程序。例如,我在控制台应用程序中使用它。

Steve Gordon 的 HttpClientFactory in ASP.NET Core 2.1 (PART 1) 是一个很好的介绍,所有他在 series 中的文章。

简而言之,每次我们想要一个 HttpClient 时,我们都会向工厂请求一个实例:

[Route("api/[controller]")]
public class ValuesController : Controller

    private readonly IHttpClientFactory _httpClientFactory;

    public ValuesController(IHttpClientFactory httpClientFactory)
    
        _httpClientFactory = httpClientFactory;
    

    [HttpGet]
    public async Task<ActionResult> Get()
    
        var client = _httpClientFactory.CreateClient();
        var result = await client.GetStringAsync("http://www.google.com");
        return Ok(result);
    

HttpClient 将工作委托给 SocketClientHandler。这就是需要重用的东西。 HttpClientFactory 生成 HttpClient 实例,这些实例重用来自套接字处理程序池的 Socket 处理程序。处理程序会定期回收以处理 DNS 更改。

更好的是,HttpClientFactory can be combined with Polly 为 HttpClient 实例添加重试逻辑。它通过配置处理程序在后台执行此操作。

【讨论】:

我了解 .net 核心解决方案,并且阅读了所有史蒂夫的系列文章。但它们都在.net核心中。我的问题清楚地说明了.net 框架而不是核心。所以回到这个问题,如果我不想将他的解决方案与 var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar/baz/123?a=ab")); sp.ConnectionLeaseTimeout = 60*1000; // 1 minute 一起使用,我应该如何实例化 httpClient ?多 ?单身的 ?对于每个端点? 它们是 .NET Standard,而不是 .NET Core。我在 .NET 旧控制台应用程序中使用它们。你的问题的答案是不要自己做,让工厂做。 我的问题不是关于 .net 标准,而是 4.6.1 使用 .NET 标准库。 .NET Standard 不是运行时,它是由 .NET Framework 或 .NET Core 等运行时提供的一组 API。面向 .NET Standard 的库可在与 .NET Standard 版本兼容的任何运行时中运行 @RoyiNamir 您已经在以一种或另一种方式使用 .NET Standard 2.0 库,无论是直接来自 Microsoft 还是其他 NuGet 包(如 JSON.NET)【参考方案2】:

ServicePointManager 在我看来是不稳定的,仅在 Windows 机器上有效。如果您从dotnet framework 转到dotnet core,则实际上是死代码。我不会依赖我的实现。

我的建议是创建一个单例服务,只为HttpClient 的一个实例提供服务。

如果您使用的是 asp.net core,他们会为您提供静态客户端的实现,并在此 document 中重新描述问题

【讨论】:

谢谢,但我没有使用核心。单例也会有 dns 更改的问题...(在 .net fw 中)。我已经知道如何在 .net 核心中处理它,但我的问题是针对非核心 @RoyiNamir 怎么样?单例意味着只有 1 个实例被有效地创建为您在整个应用程序中提供 HttpClient 的单个实例。 我了解 .net 核心解决方案,并且阅读了所有史蒂夫的系列文章。但它们都在.net核心中。我的问题清楚地说明了.net 框架而不是核心。所以回到这个问题,如果我不想将他的解决方案与 var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar/baz/123?a=ab")); sp.ConnectionLeaseTimeout = 60*1000; // 1 minute 一起使用,我应该如何实例化 httpClient ?多 ?单身的 ?对于每个端点?【参考方案3】:

我自己没有用过,但我想你可能正在寻找类似的东西:HttpClientFactoryLite。它派生了 Microsoft.Extensions.HttpClientFactory,然后删除了所有“自以为是”的部分(例如,DI、日志记录、类型化客户端等)以删除所有外部依赖项。

【讨论】:

以上是关于HttpClient 和套接字耗尽 - 澄清?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免 .NET 中 HttpClient 的 DNS 失效问题?

HttpClient连接池耗尽引发雪崩问题

服务器HttpClient连接池耗尽问题排查与解决

.NET Core HttpClientFactory+Consul实现服务发现

使用 as3httpclient 套接字闪存上传到 s3

HttpClient:每个套接字地址(协议/网络地址/端口)通常只允许使用一次