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

3

在使用 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应用程序时,调用CustomHttpClientManager的Initialize方法,实例化HttpClient。
2.客户端调用WebApi控制器的操作。
3.该操作调用业务逻辑类(服务类)。
4.逻辑类使用CustomHttpClientManager的GetAsString方法执行外部调用,使用先前实例化的HttpClient。
客户端对WebApi进行了大量调用(每分钟约200个请求),在命令提示符上运行netstat -ano时,它显示了一个相当长的池连接列表,其状态为TIME_WAIT,这表明HttpClient根本不重用来自池的连接。这些连接只有在使用后约两分钟才会消失。
除了这段代码之外,我的其他方法是:
1.在Startup中注入IHttpClientFactory,并使用.AddHttpClient()。
2.使用上述相同的类,但使用线程安全的单例版本。
以上两种方法都没有起作用。我不知道还能做什么。这让我疯狂了将近两天。
我已经从简单的控制台应用程序中测试过此实现,问题并未发生。这告诉我,某种方式WebApi操作保持关闭外部连接一旦给出其调用者的回复,一旦进行另一次调用,它就会在池中创建另一个连接,并且对于下一个传入的请求也会一直如此。
顺便说一下,我正在使用.NET Core 3.1。
提前致谢。

使用 IHttpClientFactory 是正确的方法,应该可以解决问题。不知道你尝试时出了什么问题。但这绝对是我发现最常推荐的方法。 - Fildor
很难说,没有看到那种方法。也许你只是差了一点点。 - Fildor
你设置了处理程序的生命周期吗?services.AddHttpClient<IMyService, MyService>() .SetHandlerLifetime(TimeSpan.FromMinutes(5)); - Fildor
这很重要,因为services.AddHttpClient注册了瞬态服务。所以每个请求=>新服务=>新的HttpClient...但是它们共享一个处理程序。您只需要设置其生命周期即可。 - Fildor
我刚刚按照你的建议添加了SetHandlerLifetime(TimeSpan.FromMinutes(5));,但是它仍然一遍又一遍地创建和关闭连接。 - jecarfor
显示剩余2条评论
1个回答

0

使用

PooledConnectionLifetime = TimeSpan.FromSeconds(30)

意味着连接在30秒后从池中移除。

您可以将其更改为更长的时间(例如10分钟),但不要太长以适应DNS更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接