Kestrel 是纯异步 I/O 模型,IIS 是进程/线程代理模型
Kestrel 本身不创建新线程处理每个请求,而是依赖
ThreadPool中的少量线程配合操作系统级别的异步 I/O(如 Linux 的 epoll、Windows 的 IOCP)完成大量并发连接的调度。它默认以单个
WebHost实例运行,所有请求共享同一个事件循环上下文。
IIS 不直接执行 .NET 应用代码,它作为反向代理和进程管理器:接收 HTTP 请求 → 转发给后端的
w3wp.exe进程(或
dotnet.exe,取决于托管模型)→ 由 ASP.NET Core 的
WebHost处理。这意味着请求路径多了一层转发,也引入了额外缓冲和超时配置(如
requestTimeout、
maxAllowedContentLength)。 IIS 的
applicationPool设置(如“启动模式=始终运行”、“空闲超时=0”)直接影响首次请求延迟和长连接保持能力 Kestrel 在 Linux 上可直连负载均衡器(如 Nginx),绕过 IIS;在 Windows 上若启用
InProcess托管,IIS 仅做进程宿主,不参与 HTTP 解析 高并发下,IIS 默认的
maxConcurrentRequestsPerCPU(旧版)或
dynamicTypes配置可能成为瓶颈,而 Kestrel 的吞吐更取决于 CPU 和内存带宽,而非线程数
Kestrel 的连接队列和请求超时由代码层控制,IIS 有独立的中间层超时
Kestrel 的行为由
KestrelServerOptions直接控制,比如
LimitMaxConcurrentConnections、
KeepAliveTimeout、
RequestHeadersTimeout等,这些都在应用启动时通过
ConfigureKestrel设置。它们作用于原始 TCP 连接和 HTTP 头解析阶段,不经过任何中间代理。
IIS 则在多个层级叠加超时:客户端到 IIS 的
connectionTimeout(
system.webServer/serverRuntime)、IIS 到后端的
requestTimeout(
aspNetCore元素)、以及后端 Kestrel 自身的超时。三者不一致时,最短的那个会先触发中断,且错误日志分散在不同位置(
eventvwr、
stdoutLogEnabled、
Application Insights)。 常见现象:
502.3 Bad Gateway往往是 IIS 等待后端响应超时,但 Kestrel 其实还在处理;此时需比对
aspNetCore/@requestTimeout和
Kestrel.RequestHeadersTimeoutLinux + Kestrel 场景下,
SO_BACKLOG(即
ListenBacklog)设太小会导致 SYN 包被丢弃,表现为连接拒绝而非超时——这在 IIS 下不会出现,因为 IIS 自己管理 accept 队列
KestrelServerOptions.Limits.MaxRequestBodySize默认为
30MB,而 IIS 的
maxAllowedContentLength默认仅
30MB(但单位是字节),数值相同却易因单位混淆导致上传失败
并发压测时线程池饥饿表现完全不同
当应用中存在同步阻塞调用(如
Task.Wait()、
Thread.Sleep()、未 await 的
HttpClient调用),Kestrel 的线程池线程会被快速耗尽,后续请求在
ThreadPool队列中等待,表现为延迟陡增、
ThreadPool.GetAvailableThreads返回值趋近于 0。此时 CPU 使用率可能并不高,但吞吐崩溃。
IIS 在
OutOfProcess模式下,w3wp 进程有自己的线程池,与 Kestrel 所在的
dotnet.exe进程隔离;而在
InProcess模式下,两者共用同一进程和线程池,问题表现类似 Kestrel 单独运行,但多了 IIS 的健康检查重试逻辑(如自动重启卡死的 w3wp)。 诊断方法:
dotnet-dump collect -p <pid></pid>+
dumpheap -stat查看
WaitHandle或
ManualResetEvent实例是否异常增多 IIS 的
applicationPool/@queueLength是关键指标:超过默认 1000 表示请求在 IIS 层已排队,说明后端处理不过来或网络转发延迟大 Kestrel 日志中出现大量
Connection id "..." request timed out,但没有对应的应用层异常,大概率是底层 I/O 超时而非业务卡顿
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxConcurrentConnections = 1000;
serverOptions.Limits.MaxRequestBodySize = 100 * 1024 * 1024; // 100MB
serverOptions.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
});
真正难调的不是参数本身,而是 IIS 和 Kestrel 之间那几毫秒的转发延迟、缓冲区大小差异、以及超时链路里任意一环的静默截断。线上遇到偶发 502 或连接重置,先确认哪一层先超时,再查对应日志——别只盯着
Program.cs里的配置。
