几个小时后并行发送HTTP请求的大型ServicePoint对象

时间:2015-03-17 10:18:29

标签: c# .net multithreading httpclient servicepoint

我们正在使用HttpClient并行向远程Web API发送请求:

public async Task<HttpResponseMessage> PostAsync(HttpRequestInfo httpRequestInfo)
{
    using (var httpClient = new HttpClient())
    {
        httpClient.BaseAddress = new Uri(httpRequestInfo.BaseUrl);
        if (httpRequestInfo.RequestHeaders.Any())
        {
            foreach (var requestHeader in httpRequestInfo.RequestHeaders)
            {
                httpClient.DefaultRequestHeaders.Add(requestHeader.Key, requestHeader.Value);
            }
        }

        return await httpClient.PostAsync(httpRequestInfo.RequestUrl, httpRequestInfo.RequestBody);
    }
}

此API可以由多个线程同时调用。运行大约四个小时后,我们发现内存泄漏问题发生了,从分析工具来看,似乎有两个ServicePoint个对象,其中一个很大,大约160 MB。

据我所知,我可以看到代码上面的一些问题:

  • 我们应该尽可能地分享HttpClient个实例。在我们的例子中,请求地址和标题可能会有很大差异,所以这是我们可以做某事还是不会损害太多性能的一点?我只想到我们可以准备一个字典来存储和查找HttpClient个实例。
  • 我们没有修改DefaultConnectionLimit的{​​{1}},因此默认情况下它只能同时向同一服务器发送两个请求。如果我们将此值更改为更大的值,可以解决内存泄漏问题吗?
  • 我们还禁止了HTTPS证书验证:ServicePoint这是否与此问题有关?

由于这个问题不易复制(需要很多时间),我只需要一些想法,以便我可以长时间运行优化代码。

1 个答案:

答案 0 :(得分:2)

自己解释一下情况,以防其他人以后也遇到这个问题。

首先,这不是内存泄漏,这是性能问题。

  

我们在虚拟机上测试我们的应用程序,我们打开了代理。它导致互联网很慢。因此,在我们的示例中,每个HTTP请求可能需要3-4秒。随着时间的推移,ServicePoint队列中将存在大量连接。因此,这不是内存泄漏,这只是因为以前的连接没有足够快地完成。

确保每个HTTP请求都不那么慢,一切都变得正常。

我们还尝试减少HttpClient个实例,以提高HTTP请求效果:

private readonly ConcurrentDictionary<HttpRequestInfo, HttpClient> _httpClients;

private HttpClient GetHttpClient(HttpRequestInfo httpRequestInfo)
{
    if (_httpClients.ContainsKey(httpRequestInfo))
    {
        HttpClient value;
        if (!_httpClients.TryGetValue(httpRequestInfo, out value))
        {
            throw new InvalidOperationException("It seems there is no related http client in the dictionary.");
        }

        return value;
    }

    var httpClient = new HttpClient { BaseAddress = new Uri(httpRequestInfo.BaseUrl) };
    if (httpRequestInfo.RequestHeaders.Any())
    {
        foreach (var requestHeader in httpRequestInfo.RequestHeaders)
        {
            httpClient.DefaultRequestHeaders.Add(requestHeader.Key, requestHeader.Value);
        }
    }

    httpClient.DefaultRequestHeaders.ExpectContinue = false;
    httpClient.DefaultRequestHeaders.ConnectionClose = true;
    httpClient.Timeout = TimeSpan.FromMinutes(2);

    if (!_httpClients.TryAdd(httpRequestInfo, httpClient))
    {
        throw new InvalidOperationException("Adding new http client thrown an exception.");
    }

    return httpClient;
}

在我们的例子中,只有请求主体对于相同的服务器地址是不同的。我还覆盖了Equals的{​​{1}}和GetHashCode方法。

同时,我们设置HttpRequestInfo

希望这可以帮助你。