【发布时间】:2017-09-07 00:56:49
【问题描述】:
我有一个非常简单的 API,它有一个单例实例,其中有许多 HTTPClients 在整个应用程序中重用。 API被调用,我创建了一个任务列表,每个任务调用一个客户端。 我使用 CancellationToken 和另一个任务来严格超时。
public class APIController : Controller
{
private IHttpClientsFactory _client;
public APIController(IHttpClientsFactory client)
{
_client = client;
}
[HttpPost]
public async Task<IActionResult> PostAsync()
{
var cts = new CancellationTokenSource();
var allTasks = new ConcurrentBag<Task<Response>>();
foreach (var name in list)//30 clients in list here
{
allTasks.Add(CallAsync(_client.Client[name], cts.Token));
}
cts.CancelAfter(1000);
await Task.WhenAny(Task.WhenAll(allTasks), Task.Delay(1000));
//do something with allTasks
}
}
CallAsync 也很简单,只需使用客户端调用并等待应答即可。
var response = await client.PostAsync(endpoint, content, token);
现在这段代码完美运行,1 秒后它返回,然后将取消请求发送到任何尚未返回的任务。 任务列表大约有 30 个客户端,因此 API 每次调用 30 个端点,平均响应时间为 800 毫秒。
此应用程序每秒管理 3000 个并发调用,因此大约 每秒完成 100k Httpclient 调用。
问题在于 HttpClient 中存在一些瓶颈,实际上 CPU 总是非常高,我需要大约 80 个(八十个)具有 32GB RAM 的 16 核虚拟机来处理流量。显然有问题。
我得到的一个提示是,在将我的 nugget 包更新到 Asp.net Core 2 之前,完全相同的代码执行得更好。
我在服务器上进行了诊断,我的代码中没有任何问题,但似乎 HttpClients 客户端有点相互等待或卡住了。
跟踪中确实没有其他内容。 我正在使用工厂为每个端点创建单个实例:
public class HttpClientsFactory : IHttpClientsFactory
{
public static Dictionary<string, HttpClient> HttpClients { get; set; }
public HttpClientsFactory()
{
HttpClients = new Dictionary<string, HttpClient>();
Initialize();
}
private static void Initialize()
{
HttpClients.Add("Name1", CreateClient("http://......"));
HttpClients.Add("Name2", CreateClient("http://...."));
HttpClients.Add("Name3", CreateClient("http://...."));
}
public Dictionary<string, HttpClient> Clients()
{
return HttpClients;
}
public HttpClient Client(string key)
{
try
{
return Clients()[key];
}
catch
{
return null;
}
}
public static HttpClient CreateClient(string endpoint)
{
try
{
var config = new HttpClientHandler()
{
MaxConnectionsPerServer = int.MaxValue,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
var client = new HttpClient(config)
{
Timeout = TimeSpan.FromMilliseconds(1000),
BaseAddress = new Uri(endpoint)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Connection.Clear();
client.DefaultRequestHeaders.ExpectContinue = false;
client.DefaultRequestHeaders.ConnectionClose = false;
client.DefaultRequestHeaders.Connection.Add("Keep-Alive");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
catch (Exception)
{
return null;
}
}
}
然后在启动中
services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>();
这里发生了什么,HttpClient 的单例不适合这种情况吗? 我应该为每个线程创建一个 HttpClient 实例吗?我该怎么做?
更新
经过几天的测试,我确信 HTTPClient 调用期间的超时会使一些连接处于打开状态,从而导致端口耗尽。 关于如何避免这种情况的任何建议?
【问题讨论】:
-
我最初的想法是客户端太多,并且已知会导致问题。 HttpClient 旨在让一个客户端在应用程序的整个生命周期中重复使用。从工厂方法来看,除了基本 url 之外,客户端之间似乎没有太大区别。
-
与 HttpClient 的传出连接数存在限制:请查看此博客条目 - blogs.msdn.microsoft.com/timomta/2017/10/23/…
标签: asp.net-core dotnet-httpclient