【问题标题】:.Net5 HttpClient concurrency - performance.Net5 HttpClient 并发 - 性能
【发布时间】:2021-06-13 19:34:26
【问题描述】:

使用 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 无延迟支持多少并行请求。
  • 如何针对 2000 个或更多并行请求提高 HttpClient 的性能..

【问题讨论】:

  • 您是否考虑过 API 可能是瓶颈?
  • 对您作为开发人员一无所知,我不得不想知道您是如何将数千个操作排队以并行运行的。有许多简单的方法可以做到这一点,这会导致很大的性能损失。作为第一步,我会说用 no-ops 替换你的 HTTP 调用,看看运行需要多长时间。
  • @FranzGleichmann API 又是一个示例 ASP.Netcore Web API,它通过示例响应响应请求,而不需要像 I/O 这样的耗时活动。
  • 延迟听起来像是内存问题。最初的缓慢响应是系统正在尝试获取所需的内存来保存结果。一旦应用程序获得了所需的内存,操作就会很快。
  • @KevinKrumwiede 用示例代码更新了帖子

标签: c# .net-core httpclient asp.net5


【解决方案1】:

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);

【讨论】:

    【解决方案2】:

    您正在做的事情是导致“冷”(刚刚更新或空连接池)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);
       }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多