请求在 Azure AppService 中排队,尽管它在线程池中有足够的线程

Requests are queuing in Azure AppService though it has enough threads in threadpool

我已经使用 asp.net webapi 编写了一个 api 并将其作为 Appservice 部署在 azure 中。我的控制器名称是 TestController,我的操作方法类似于下面的内容。

    [Route("Test/Get")]
    public string Get()
    {
        Thread.Sleep(10000);
        return "value";
    }

因此对于每个请求,它应该在 return 字符串 "value" 之前等待 10 秒。我还编写了另一个端点来查看线程池中用于执行请求的线程数。这个动作就像下面这样。

    [Route("Test/ThreadInfo")]
    public ThreadPoolInfo Get()
    {
        int availableWorker, availableIO;
        int maxWorker, maxIO;

        ThreadPool.GetAvailableThreads(out availableWorker, out availableIO);
        ThreadPool.GetMaxThreads(out maxWorker, out maxIO);

        return new ThreadPoolInfo
        {
            AvailableWorkerThreads = availableWorker,
            MaxWorkerThreads = maxWorker,
            OccupiedThreads = maxWorker - availableWorker
        };
    }

现在,当我们同时对 Test/Get 端点进行 29 次 get 调用时,几乎需要 11 秒才能使所有请求成功。所以服务器在 11 个线程中并发执行所有请求。要查看线程状态,在调用 Test/Get returns 后立即调用 Test/ThreadInfo(无需等待) { "AvailableWorkerThreads": 8161, "MaxWorkerThreads": 8191, "OccupiedThreads": 30 }

似乎有 29 个线程正在执行 Test/Get 请求,1 个线程正在执行 Test/ThreadInfo 请求。

当我对 Test/Get 进行 60 次 get 调用时,几乎需要 36 秒才能成功。调用 Test/ThreadInfo(需要一些时间)returns { "AvailableWorkerThreads": 8161, "MaxWorkerThreads": 8191, "OccupiedThreads": 30 }

如果我们增加请求数,OccupiedThreads 的值就会增加。就像 1000 个请求需要 2 分 22 秒,OccupiedThreads 的值为 129。

似乎请求并在 30 个并发调用后排队,尽管 WorkerThread 中有很多线程可用。它逐渐增加并发执行的线程,但这还不够(1000 个请求需要 129 个)。

由于我们的服务有很多 IO 调用(其中一些是外部 api 调用,一些是数据库查询),延迟也很高。由于我们使用所有 IO 调用异步方式,因此服务器可以同时处理大量请求,但当处理器执行实际工作时我们需要更多的并发性。我们在一个实例中使用 S2 服务计划。增加实例会增加并发,但我们需要单实例的更多并发。

阅读有关 IIS 的一些博客和文档后,我们看到有一个设置 minFreeThreads。如果线程池中的可用线程数低于此设置的值,IIS 将开始对请求进行排队。 appservice里面有这样的吗?是否真的有可能从 Azure 应用服务获得更多并发性,或者我们在那里缺少一些配置?

据我了解,您需要通过 ThreadPool.GetMinThreads 检查 MinWorkerThreads,它可以检索 ThreadPool 在预期新请求时维护的空闲线程数。我建议您在执行操作 (Tetst/Get) 后 return 当前线程池信息,而不是向 Test/ThreadInfo 发出新请求。根据您的代码,我在我的 Web 应用程序上使用 B1 定价层对其进行了测试,如下所示:

1000 个并发请求,每个动作休眠 15 秒,总共耗时 3 分 20 秒。

线程池创建和销毁工作线程以优化吞吐量,如果当前线程可以处理请求,则不会创建更多线程。一旦这些线程执行完它们的活动,它们就会 return 进入线程池。

然后,我用了Asynchronous programming,把动作改成如下:

public async Task<ActionResult> DoJob(int id)
{
    await Task.Delay(TimeSpan.FromSeconds(15));
    return ThreadInfo();
}

结果:

近3000个并发请求:

一般来说,AvailableWorkerThreads只是表示已经为你创建了可以代替工作线程启动的额外工作线程的数量。我会建议您使用异步编程并将您的实际工作部署在 azure web 应用程序上,然后检查实际性能并找到相关方法,如果有任何瓶颈。

我的问题终于有了答案。事情是 ASP.NET 线程池维护了一个线程池,这些线程已经产生了线程初始化成本并且易于重用。 .NET 线程池也是自调整的。它监视 CPU 和其他资源利用率,并根据需要添加新线程或调整线程池大小。当有很多请求并且池中没有足够的线程可用时,线程池开始在池中添加新线程,在此之前它运行自己的算法来查看内存状态和 cpu 系统使用情况需要很长时间,这就是为什么我们看到池中的工作线程缓慢增加并且有很多请求排队。但幸运的是,在线程池切换到添加新线程的算法之前,有一个选项可以设置工作线程的数量。代码如下所示。

    public string Settings()
    {
        int minWorker, minIOC;
        ThreadPool.GetMinThreads(out minWorker, out minIOC);

        if (ThreadPool.SetMinThreads(300, minIOC)){ 
            return "The minimum number of threads was set successfully.";
        }
        else
        {
            return "The minimum number of threads was not changed.";
        }

    }

此处 ThreadPool.SetMinThreads(300, minIOC) 设置线程池在切换到添加或删除线程的算法之前将创建的最小线程值。我已将此方法添加为我的 webapi 控制器的一个操作,然后在 运行 此操作之后,当我向 Test/Get 端点发出 300 个并发请求时发出请求,所有这些都是 运行 并在 11 中完成秒,没有请求排队。