ASP.NET Core / Kestrel中的线程管理

17

我正在解决一个性能/可扩展性问题,我们将一个asp.net应用迁移到了asp.net core 2.0。我们的应用作为应用服务在Azure上托管,并且在任何中等流量下都很容易崩溃。

有一件令我困惑的事情是如何处理多个并发请求。根据我在这里读到的,Kestrel使用多个事件循环来处理您的请求。但实际用户代码是在.NET线程池上处理的(来自此处)。

所以,作为一个实验 - 我创建了一个新的asp.net core 2.0 MVC应用程序,并添加了一个相当恶劣的操作方法:

    [AllowAnonymous]
    public ActionResult Wait1()
    {
        System.Threading.Tasks.Task.Delay(1000).Wait();
        return new StatusCodeResult((int)HttpStatusCode.OK);
    }     

现在,当我将此推送到Azure时,如果我同时发送100个请求,那么我应该没问题,因为100个请求听起来像是轻负载,对吧?等待将发生在线程池线程上,对吗?

所以-我就这样做了,并得到了一些相当糟糕的结果-示例用红色突出:

enter image description here

嗯,不是我期望的结果,每个请求大约需要50秒...但是,如果我更改频率,使请求间隔一秒钟,那么响应时间就很好-回到预期的1000ms左右。似乎如果我一次超过30个请求,它就开始遇到问题,这对我来说有点低。

所以-我意识到我的恶劣作用方法会阻塞,但我本来希望它在线程池线程上阻塞,从而能够处理比我的30个更多的请求。

这是预期行为吗-如果是,那么只需确保不使用异步代码进行IO绑定工作即可吗?


你的应用服务运行在哪种实例类型上? - Chris Pratt
S3标准,虽然它有3个插槽,所以流量在一个非核心的aspnet Web应用程序和这个应用程序之间进行共享,还有一个暂存插槽。 - Matt Roberts
没问题,我只是想排除一个明显的原因,如果你正在运行 F1 或 D1 实例的话。 - Chris Pratt
1个回答

7

我想这是预期行为 - 如果是这样的话,那么只需要确保没有使用异步代码进行IO限制的工作即可。

根据我的经验,这似乎是预期的行为。我们可以从这里blog得到答案。

现在假设您正在IIS上运行ASP.Net应用程序,并且您的Web服务器有四个CPU。 假设在任何给定时间点,有100个请求需要处理。 默认情况下,运行时将创建四个线程,可用于服务前四个请求。 因为在500毫秒过去之前不会添加任何其他线程,所以其他96个请求必须等待队列。 在500毫秒过去后,将创建一个新线程。

正如您所看到的,需要100 * 500ms的时间间隔才能赶上工作量。

这是使用异步编程的一个很好的理由。使用异步编程,当请求被处理时,线程不会被阻塞,因此这四个线程几乎立即就能被释放。
我建议您可以使用异步代码来提高性能。
public async Task<ActionResult> Wait1()
{
    await Task.Delay(TimeSpan.FromSeconds(15));
    return new StatusCodeResult((int)HttpStatusCode.OK);
}

我还找到另一个SO thread,你可以参考一下。

4
干杯!奇怪的是,我有一个非Core版本的asp.net网站,它比asp.net core版本更好地处理了负载。我同意异步编程在某种程度上是解决方案之一,但我仍然困惑为什么Core处理这个测试时表现如此糟糕。 - Matt Roberts
你的老派应用程序可能配置了一个很高的minthreads值,这对于现代异步应用程序来说是不合适的。一个设计良好的应用程序只使用与核心数量相当的线程数。 - George Polevoy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接