Kestrel 是否使用单线程来处理像 Node.js 这样的请求?

Is Kestrel using a single thread for processing requests like Node.js?

两者都Kestrel and Node.js are based on libuv

虽然 Node.js 确切地说它使用 an event loop,但我似乎无法找到 Kestrel 是否属于这种情况,或者它是否像 IIS 一样利用线程池/请求队列?

Web 服务器后面的 Kestrel

Node.js 事件循环

    ┌───────────────────────┐
 ┌─>│        timers         │
 │  └──────────┬────────────┘
 │  ┌──────────┴────────────┐
 │  │     I/O callbacks     │
 │  └──────────┬────────────┘
 │  ┌──────────┴────────────┐
 │  │     idle, prepare     │
 │  └──────────┬────────────┘      ┌───────────────┐
 │  ┌──────────┴────────────┐      │   incoming:   │
 │  │         poll          │<─────┤  connections, │
 │  └──────────┬────────────┘      │   data, etc.  │
 │  ┌──────────┴────────────┐      └───────────────┘
 │  │        check          │
 │  └──────────┬────────────┘
 │  ┌──────────┴────────────┐
 └──┤    close callbacks    │
    └───────────────────────┘

更新 ASP.Net Core 2.0。正如 poke 所指出的,服务器已在托管和传输之间拆分,其中 libuv 属于传输层。 libuv ThreadCount 已移动到它自己的 LibuvTransportOptions 并且它们在您的网络主机构建器中使用 UseLibuv() ext 方法单独设置:

  • 如果你勾选github中的LibuvTransportOptionsclass,你会看到一个ThreadCount选项:

    /// <summary>
    /// The number of libuv I/O threads used to process requests.
    /// </summary>
    /// <remarks>
    /// Defaults to half of <see cref="Environment.ProcessorCount" /> rounded down and clamped between 1 and 16.
    /// </remarks>
    public int ThreadCount { get; set; } = ProcessorThreadCount;
    
  • 可以在您的虚拟主机构建器中调用 UseLibuv 来设置该选项。例如:

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseLibuv(opts => opts.ThreadCount = 4)
            .UseStartup<Startup>()                
            .Build();
    

在 ASP.NET Core 1.X 中,Libuv 配置是 kestrel 服务器的一部分:

  • 如果你在它的 github 仓库中检查 KestrelServerOptions class,你会看到有一个 ThreadCount 选项:

    /// <summary>
    /// The number of libuv I/O threads used to process requests.
    /// </summary>
    /// <remarks>
    /// Defaults to half of <see cref="Environment.ProcessorCount" /> rounded down and clamped between 1 and 16.
    /// </remarks>
    public int ThreadCount { get; set; } = ProcessorThreadCount;
    
  • 可以在对 UseKestrel 的调用中设置该选项,例如在新的 ASP.Net 核心应用程序中:

    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel(opts => opts.ThreadCount = 4)
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();
    
        host.Run();
    }
    

深入挖掘源代码:

  • 您可以看到 libuv 侦听器线程(或 KestrelThreads) being created in the KestrelEngine
  • 有些地方会叫ThreadPool methods so they can run code in the CLR Thread Pool instead of the libuv threads. (Using ThreadPool.QueueUserWorkItem). The pool seems to be defaulted with a max of 32K threads which can be modified via config
  • Frame<TContext> 委托给实际应用程序(如 ASP.Net 核心应用程序)来处理请求。

所以我们可以说它为 IO 使用了多个 libuv 事件循环。实际工作是使用 CLR 线程池在具有标准工作线程的托管代码上完成的。

我很想找到关于此的更多权威文档(official docs don't give much detail). The best one I have found is Damian Edwards talking about Kestrel on channel 9。他在第 12 分钟左右解释说:

  • libuv 使用单线程事件循环模型
  • Kestrel 支持多个事件循环
  • Kestrel 仅在 libuv 事件循环上进行 IO 工作
  • 所有非 IO 工作(包括与 HTTP 相关的任何事情,如解析、框架等)都是在标准 .net 工作线程上的托管代码中完成的。

此外,快速搜索返回:

  • David Fowler 谈论 Kestrel 中的线程池 here。它还确认请求可能仍会在 ASP.Net Core 中的线程之间跳转。 (与以前的版本一样)
  • 这个blogpost出来的时候看Kestrel
  • question 关于如何在 ASP.Net Core 中管理线程。

线程是特定于传输的。使用 libuv 传输(2.0 中的默认设置),如 的回答中所述,有许多基于机器上逻辑处理器数量的事件循环,并且可以通过设置选项的值来覆盖。默认情况下,每个连接都绑定到一个特定的线程,所有 IO 操作都在该线程上进行。用户代码在线程池线程上执行,因为我们不相信用户不会阻塞 IO 线程。当您对这些线程池线程(即 HttpResponse.WriteAsync)进行 IO 调用时,kestrel 会将其编组回套接字绑定到的适当 IO 线程。典型的请求流程如下所示:

[从网络读取]分派到线程池 -> [http 解析],[执行中间件管道] 调用写入 -> 将用户工作排队到 IO 线程[写入网络]

当然,您可以随时告诉 kestrel 您是专业人士,绝不会阻塞 IO 线程和 运行 您的代码。但我不会,除非我知道我在做什么(我不知道 :D)。