使用WebApi中的静态或单个HttpClient实例,连接池中的多个连接处于TIME_WAIT状态

问题描述

使用HttpClient时,我一直在努力阻止池中的多个连接

我已经构建了一个REST WebApi,通过在其中使用HttpClient调用了外部端点。客户端多次调用控制器的操作,以执行上述外部调用

以前,我使用的是WebClient,但是在发出多个请求时却遭受SocketExhaustation的困扰。在网络上搜索时,我发现迁移到HttpWebClient可以解决静态或单例使用的问题。我什至尝试实现IHttpClientFactory,但是它没有用,因为我在注入的服务中遇到了同样的问题。

经过多次重构,这是我当前的实现。抱歉,如果当前该代码不是最佳代码,但是在多次重写之后,这是我的最后选择。

public static class CustomHttpClientManager
    {
        private static HttpClient _httpClient;

        public static void Initialize()
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

            ServicePointManager.ServerCertificateValidationCallback +=
                    (sender,cert,chain,sslPolicyErrors) => true;

            CookieContainer cookies = new CookieContainer();
            _httpClient = new HttpClient(
            new SocketsHttpHandler()
            {
                Proxy = new CustomProxy(),UseProxy = true,PooledConnectionLifetime = TimeSpan.FromSeconds(30),PooledConnectionIdleTimeout = TimeSpan.FromSeconds(20),MaxConnectionsPerServer = 2,CookieContainer = cookies,AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
            });

            _httpClient.DefaultRequestHeaders.ConnectionClose = false;
        }

        public static async Task<string> GetAsstring(string url){
            HttpResponseMessage response = null;
            try{

                HttpRequestMessage httpRequestMessage = new HttpRequestMessage()
                {
                    RequestUri = new Uri(url),Method = HttpMethod.Get
                };

                httpRequestMessage.Headers.Add("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
                httpRequestMessage.Headers.Add("Accept-Encoding","gzip,deflate,sdch");
                httpRequestMessage.Headers.Add("Accept-Language","en-GB,en-US;q=0.8,en;q=0.6");
                httpRequestMessage.Headers.Add("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.3");
                
                response = await _httpClient.SendAsync(httpRequestMessage);    
                if(response.IsSuccessstatusCode){
                    return await response.Content.ReadAsstringAsync();   
                }
                else{
                    throw new Exception(ex.Message);
                }
            }
            catch(Exception ex){
                return $"Could not fetch response from: {connectionParameters.Url},due to: {ex.Message}";     
            }
            finally{
                if(response != null){
                    response.dispose();
                }
            }
        }
    }

直到到达当前代码之前,调用堆栈都是这样的:

  1. 在启动时,WebApi应用程序调用{​​{1}}的{​​{1}}方法,该方法仅实例化CustomHttpClientManager一次。
  2. 客户端调用WebApi控制器的动作。
  3. 该操作调用业务逻辑类(服务类)
  4. 逻辑类使用Initialize的{​​{1}}方法通过使用先前实例化的HttpClient来执行外部调用

客户端对WebApi进行了大量调用(每分钟约200 rq),并且在命令提示符下运行CustomHttpClientManager时,它会显示很长的池连接列表,其状态为TIME_WAIT,显示GetAsstring根本没有重用来自池的连接。这些连接仅在使用后2分钟左右消失。

除了此代码外,我的其他方法也是。

  • 使用HttpClient在启动时注入netstat -ano
  • 使用与上面相同的类,但使用线程安全的单例版本。

他们都没有工作。我不知道该怎么办这使我连续两天发疯。

我已经从一个简单的控制台应用程序测试了此实现,但问题并未发生。这表明我,WebApi操作以某种方式会在给定其调用方答复后继续关闭外部连接,并且一旦再次进行调用,它将在池中创建另一个连接,并继续处理下一个传入的请求。

顺便说一句,我正在使用.NET Core 3.1

预先感谢

解决方法

使用

PooledConnectionLifetime = TimeSpan.FromSeconds(30),

表示连接在 30 秒后从池中移除。

您可以将其更改为更长的时间(例如 10 分钟) - 机器人不会太长而无法容纳 DNS 更改。