问题描述
使用 IHttpClientFactory 创建了一个 HttpClient 并向 WebApi 并行发送 1000 个 GET 调用,并观察到每个请求大约有 3-5 分钟的延迟.. 完成后再次并行发送 1000 个 GET 请求,这次没有延迟。
现在我将并行请求增加到 2000,对于第一批,每个请求延迟大约为 9-11 分钟。对于第二个 2000 个并行请求,每个请求的延迟约为 5 分钟(在 1000 个请求的情况下没有延迟。)
var client = _clientFactory.CreateClient();
client.BaseAddress = new Uri("http://localhost:5000");
client.Timeout = TimeSpan.FromMinutes(20);
List<Task> _task = new List<Task>();
for (int i = 1; i <= 4000; i++)
{
_task.Add(ExecuteRequest(client,i));
if (i % 2000 == 0)
{
await Task.WhenAll(_task);
_task.Clear();
}
}
private async Task ExecuteRequest(HttpClient client,int requestId)
{
var result = await client.GetAsync($"Performance/{requestId}");
var response = await result.Content.ReadAsstringAsync();
var data = JsonConvert.DeserializeObject<Response>(response);
}
试图理解,
解决方法
HttpClient 无延迟支持多少个并行请求。
在现代 .NET Core 平台上,您仅受可用内存的限制。默认情况下没有内置的限制。
如何针对 2000 个或更多并行请求提高 HttpClient 的性能。
听起来您的服务器正在限制您。如果您想测试可扩展性更强的服务器,请尝试在您的服务器启动时运行:
var desiredThreads = 2000;
ThreadPool.GetMaxThreads(out _,out var maxIoThreads);
ThreadPool.SetMaxThreads(desiredThreads,maxIoThreads);
ThreadPool.GetMinThreads(out _,out var minIoThreads);
ThreadPool.SetMinThreads(desiredThreads,minIoThreads);
,
您正在做的是导致“冷”(刚刚更新或空连接池)HttpClient
的最坏情况性能。
当您发出新请求时,它会在连接池中查找打开的连接。当它没有找到时,它会尝试打开一个新连接。通过向冷客户端突然爆发,大多数对 SendAsync
的调用最终都会尝试打开新连接。
这是一个问题,因为需要新连接的请求将需要多次往返服务器,而现有连接上的请求只需要一次往返。如果您使用 HTTPS,情况会更糟。在这种情况下,您严重依赖网络延迟。
如果您只是进行基准测试,那么您需要对稳态性能进行基准测试,而不是热身性能。 Benchmark.NET 或多或少应该为您做这件事。
当您的请求完成得相当快时,将初始并发限制为总请求的较小百分比,然后从那里慢慢增加连接池大小可能会快得多。这允许后续请求重新使用连接。您可能会尝试像下面这样,它只会允许(粗略的行为,而不是保证)一次打开 10 个新连接:
var sem = new SemaphoreSlim(10);
var client = new HttpClient();
async Task<HttpResponseMessage> MakeRequestAsync(HttpRequestMessage req)
{
Task t = sem.WaitAsync();
bool openNew = t.IsCompleted;
await t;
try
{
return await client.SendAsync(req);
}
finally
{
sem.Release(openNew ? 2 : 1);
}
}