【问题标题】:Troubleshoot threadpool starvation under heavy load解决重负载下的线程池饥饿问题
【发布时间】:2023-01-07 09:08:17
【问题描述】:

我们的 dotnet-core (3.1) 应用程序遇到高负载问题。

超过一定数量的连接(虚拟用户),我们遇到了瓶颈,服务器饥饿,我们得到请求超时,但进程没有崩溃(没有红隼日志)。我们正在使用 K6 来对我们的应用程序进行基准测试。目前,负载测试仅在登录页面上执行 GET 请求,这会在一个小数据集(无连接等)上触发一个基本的 SQL 请求。

我们使用 Visual Studio 2019 Perfomance Profiler 工具和 perfview 来调查这个问题,但是这些工具都没有帮助我们识别导致这个瓶颈的代码部分。

我找到了这篇关于线程池饥饿的文章:https://learn.microsoft.com/fr-fr/archive/blogs/vancem/diagnosing-net-core-threadpool-starvation-with-perfview-why-my-service-is-not-saturating-all-cores-or-seems-to-stall 当我们使用任意值调整最小 ThreadPool 时,如后例所示,我们在性能上有了巨大的改进(不在图表上)。这似乎是一个权宜之计,使用它有多糟糕?

System.Threading.ThreadPool.SetMinThreads(200, 200);

解释:2C_2G/100.csv => 2 核,2Go RAM,100 个虚拟用户

环境:

  • nginx 作为反向代理
  • K6 作为基准工具
  • dotnet-core 3.1(带有 EntityFramework)
  • 操作系统:Ubuntu 20.04
  • mariadb 作为数据库

【问题讨论】:

  • 是的,这是权宜之计。你可能想调查为什么你得到线程池饥饿。可能是由于处理传入 HTTP 请求的线程池线程上的阻塞 IO 请求引起的。您应该查看async 和任务。没有代码,我们无法提供进一步的帮助。
  • 我们已经在使用异步和任务。
  • 清楚地某物正在阻塞。我建议你仔细检查你的代码。

标签: c# nginx .net-core mariadb kestrel


【解决方案1】:

您在线程池上执行长时间运行的代码。

这是使用 Task.Run 执行此操作的方法:

public async Task<byte> CalculateChecksumAsync(Stream stream) => await Task.Run(() =>
{
    int i;
    byte checksum = 0;
    while ((i = stream.ReadByte()) >= 0)
    {
        checksum += (byte)i;
    }
    return checksum;
});

对于看起来完全异步代码的不经意的观察者来说,因为有 async/await 和Task无处不在。

但事实上,只要它需要,就会占用一个线程池线程 读取流(这不仅取决于通过的数据量,还取决于 流的带宽)。

当线程池被饿死时,会有一秒钟的延迟 线程池将产生一个新线程。这意味着随后调用 Task.Run他们的工作会延迟那么久即使你的 CPU 闲置.

备择方案:

  • 尽可能使用异步方法而不是同步方法(例如Stream.ReadAsync),特别是当你在线程池中时
  • 为长时间运行的代码生成长时间运行的任务:
    public async Task<byte> CalculateChecksumAsync(Stream stream) => await Task.Factory.StartNew(() =>
    {
        int i;
        byte checksum = 0;
        while ((i = stream.ReadByte()) >= 0)
        {
            checksum += (byte)i;
        }
        return checksum;
    },
    TaskCreationOptions.LongRunning);
    

TaskCreationOptions.LongRunning 标志告诉 C# 你想要一个新线程 立即产生只是为了你的工作。

【讨论】:

    【解决方案2】:

    是的,增加最小工作线程数不是解决方案,而是止损器。

    看来您能够重现该问题。在这种情况下,我建议使用 dotnet-dump 找出阻塞代码的位置。按照这个YouTube Video on diagnosing thread pool starvation中的步骤,还是蛮有效的。

    顺便说一句,对于 gap-stopper 代码,我会阅读并保留异步 IO 池计数的第二个参数(如果这不会造成任何问题),并检查调用的设置结果:

    int minWorker, minIOC;
    // Get the current settings.
    ThreadPool.GetMinThreads(out minWorker, out minIOC);
    // Change the minimum number of worker threads to four, but
    // keep the old setting for minimum asynchronous I/O 
    // completion threads.
    if (ThreadPool.SetMinThreads(200, minIOC))
    {
        // The minimum number of threads was set successfully.
    }
    else
    {
        // The minimum number of threads was not changed.
    }
    

    【讨论】:

      猜你喜欢
      • 2012-07-26
      • 1970-01-01
      • 2016-06-23
      • 1970-01-01
      • 1970-01-01
      • 2021-06-08
      • 2021-02-20
      • 2020-08-05
      • 1970-01-01
      相关资源
      最近更新 更多