乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - 浅析ASP.NET Core全新跨平台内置Web服务器实现Kestrel(红隼)

什么是Kestrel

https://github.com/dotnet/aspnetcore

image

Kestrel(红隼)是一个跨平台的Web服务器实现

image

Kestrel是包含在ASP.NET Core项目模板中的Web服务器,默认处于启用状态。

Kestrel支持以下方案:

  • HTTPS
  • HTTP/2(在macOS†上除外)
  • 用于启用WebSocket的不透明升级
  • 用于获得Nginx高性能的Unix套接字

macOS的未来版本将支持HTTP/2。

.NET Core支持的所有平台和版本均支持Kestrel。

Kestrel与HTTP.sys

Kestrel服务器是默认跨平台HTTP服务器实现。Kestrel提供了最佳性能和内存利用率,但它没有HTTP.sys中的某些高级功能。

与HTTP.sys相比,Kestrel具有以下优势:

  • 更好的性能和内存利用率。
  • 跨平台
  • 灵活性,它是独立于操作系统进行开发和修补的。
  • 编程端口和TLS配置
  • 扩展性,允许PPv2等协议和备用传输。

Http.Sys作为共享内核模式组件运行,具有kestrel不具备的以下功能:

  • 端口共享
  • 内核模式Windows身份验证。Kestrel仅支持用户模式的身份验证。
  • 通过队列传输的快速代理
  • 直接文件传输
  • 响应缓存

前生今世

2014年11月,ASP.NET 5(后来改名为ASP.NET Core)宣布成为跨平台技术。

显然,微软需要一种新的设计来支持Windows,macOS和Linux。而iis和window的强耦合基本上是无法移植到其他平台上的。

Kestrel是微软为了托管跨平台的ASP.NET Core定制的一款应用服务器容器。

.Net Hosting包括三种解决方案,Kestrel、HTTP.sys、IIS,其中IIS根据Hosting Models还可以细分为进程内托管(In Prcoess)进程外托管(Out Of Process)

Kestrel

image

Kestrel可以作为独立的Process处理外部的HTTP Request,如下图

image

但因为Kestrel需要独占Port,在Multi-Tenant的部署环境,可以结合Reverse Proxy例如IIS、Nginx以及Apache来处理Http Request

image

HTTP.sys

HTTP.sys全称为HTTP.sys Web Server,很容易和IIS扮演Listener同的HTTP.sys Driver搞混。

image

HTTP.sys WebServer是由HTTP.sys Driver搭配HTTP Server API所构成,可以替代Kestrel使用,同样可以直接面对Http Request。

HTTP.sysWindows Server平台限定,不同于Kestrel可以跨平台,而使用HTTP.sys的主要用途是处理Kestrel所无法处理的工作,例如Kernel Mode Windows Auth

但在一般情况下Microsoft是推荐优先使用Kestrel的噢

image

HTTP.sys Web Server的一个特色是无法搭配IIS使用,尽管两者同样是HTTP.sys Driver负责Listen Http Request。

IIS

进程外托管(Out Of Process)

Out Of Process Hosting Mode相当于Kestrel使用Proxy的模式,对外是由IIS的w3wp.exe来接待Http Request,再交由dotnet.exe处理。

image

参见:使用IIS和ASP.NET Core进行进程外托管

进程内托管(In Prcoess)

In Prcoess Hosting Mode,系统只会有独立的Process,并且使用的是IIS Http Server而非Kestrel,这种架构免去了Proxy代理的往返过程,效能上更为跃进,同时In Process也是预设将.NET部署至IIS所采用的模式。

image

参见:使用IIS和ASP.NET Core进行进程内托管

源码迁移

最早Kestrel(紅隼)的代码是单独维护在https://github.com/aspnet/KestrelHttpServer,随着ASP.NET Core的发展,已经全面迁移到主库一起维护了:https://github.com/aspnet/AspNetCore

image

默认启用

未使用IIS托管时,ASP.NET Core项目模板默认使用Kestrel。在下面的模板生成的Program.cs中,WebApplication.CreateBuilder方法在内部调用UseKestrel

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}
//
// 摘要:
//     Extension methods for configuring the IWebHostBuilder.
public static class GenericHostBuilderExtensions
{
    //
    // 摘要:
    //     Initializes a new instance of the Microsoft.AspNetCore.Hosting.IWebHostBuilder
    //     class with pre-configured defaults.
    //
    // 参数:
    //   builder:
    //     The Microsoft.Extensions.Hosting.IHostBuilder instance to configure
    //
    //   configure:
    //     The configure callback
    //
    // 返回结果:
    //     The Microsoft.Extensions.Hosting.IHostBuilder for chaining.
    //
    // 言论:
    //     The following defaults are applied to the Microsoft.AspNetCore.Hosting.IWebHostBuilder:
    //     use Kestrel as the web server and configure it using the application's configuration
    //     providers, adds the HostFiltering middleware, adds the ForwardedHeaders middleware
    //     if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true, and enable IIS integration.
    public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure);
}

WebApplication.CreateBuilder的UseKestrel

internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
    builder.ConfigureAppConfiguration((ctx, cb) =>
    {
        if (ctx.HostingEnvironment.IsDevelopment())
        {
            StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
        }
    });
    builder.UseKestrel((builderContext, options) =>
    {
        options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);
    })
    .ConfigureServices((hostingContext, services) =>
    {
        // Fallback
        services.PostConfigure<HostFilteringOptions>(options =>
        {
            if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
            {
                // "AllowedHosts": "localhost;127.0.0.1;[::1]"
                var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                // Fall back to "*" to disable.
                options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
            }
        });
        // Change notification
        services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
                    new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));

        services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
        services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
        services.AddTransient<IConfigureOptions<ForwardedHeadersOptions>, ForwardedHeadersOptionsSetup>();

        services.AddRouting();
    })
    .UseIIS()
    .UseIISIntegration();
}

一般限制

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel
            });
            webBuilder.UseStartup<Startup>();
        });
{
  "Kestrel": {
    "Limits": {
      "MaxConcurrentConnections": 100,
      "MaxConcurrentUpgradedConnections": 100
    },
    "DisableStringReuse": true
  }
}

保持活动状态超时

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 保持活动状态超时
                kestrelServerOptions.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
            });
            webBuilder.UseStartup<Startup>();
        });

客户端最大连接数

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 设置最大打开的连接数
                kestrelServerOptions.Limits.MaxConcurrentConnections = 100;

                // 设置最大打开、升级的连接数,升级的连接是已从HTTP切换到另一个协议(如WebSocket)的连接
                kestrelServerOptions.Limits.MaxConcurrentUpgradedConnections = 100;
            });
            webBuilder.UseStartup<Startup>();
        });

请求正文最大大小

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 设置允许的请求正文的最大大小(以字节为单位)
                kestrelServerOptions.Limits.MaxRequestBodySize = 100_000_000;
            });
            webBuilder.UseStartup<Startup>();
        });

请求正文最小数据速率

Kestrel每秒检查一次数据是否以指定的速率(字节/秒)传入。如果速率低于最小值,则连接超时。宽限期是Kestrel允许客户端将其发送速率提升到最小值的时间量。在此期间不会检查速率。宽限期有助于避免最初由于TCP慢启动而以较慢速率发送数据的连接中断。最小速率也适用于响应。

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 设置请求正文的最小数据速率(以字节/秒为单位)
                kestrelServerOptions.Limits.MinRequestBodyDataRate = new MinDataRate(
                    bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));

                // 设置响应最小数据速率(以字节/秒为单位)
                kestrelServerOptions.Limits.MinResponseDataRate = new MinDataRate(
                    bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
            });
            webBuilder.UseStartup<Startup>();
        });

请求标头超时

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 设置服务器接收请求头所需的最大时间量
                kestrelServerOptions.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(1);
            });
            webBuilder.UseStartup<Startup>();
        });

HTTP/2限制

每个连接的最大流

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 限制每个HTTP/2 连接的并发请求流的数量(拒绝过多的流)
                kestrelServerOptions.Limits.Http2.MaxStreamsPerConnection = 100;
            });
            webBuilder.UseStartup<Startup>();
        });

标题表大小

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 限制了服务器上HPACK编码器与解码器可以使用的标头压缩表的大小(以八进制数表示)
                kestrelServerOptions.Limits.Http2.HeaderTableSize = 4096;
            });
            webBuilder.UseStartup<Startup>();
        });

最大帧大小

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 指示允许接收的最大帧有效负载的大小(以八进制数表示)
                kestrelServerOptions.Limits.Http2.MaxFrameSize = 16_384;
            });
            webBuilder.UseStartup<Startup>();
        });

最大请求标头大小

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 指示请求头字段序列的最大允许大小
                kestrelServerOptions.Limits.Http2.MaxRequestHeaderFieldSize = 8192;
            });
            webBuilder.UseStartup<Startup>();
        });

初始连接窗口大小

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 服务器一次愿意接收和缓冲多少请求正文数据
                kestrelServerOptions.Limits.Http2.InitialConnectionWindowSize = 131_072;
            });
            webBuilder.UseStartup<Startup>();
        });

初始流窗口大小

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 服务器愿意为每个流一次接收和缓冲多少请求正文数据
                kestrelServerOptions.Limits.Http2.InitialStreamWindowSize = 98_304;
            });
            webBuilder.UseStartup<Startup>();
        });

保持活动ping配置

Kestrel可以配置为向连接的客户端发送HTTP/2 Ping。

HTTP/2 Ping有多种用途:

  • 使空闲连接保持活动状态。某些客户端和代理服务器会关闭空闲的连接。HTTP/2 Ping是对连接执行的活动,可防止空闲连接被关闭。
  • 关闭不正常的连接。服务器会关闭在配置的时间内客户端未响应保持活动ping的连接。
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 配置ping间隔的TimeSpan。如果服务器在此时间段内没有收到任何帧,则服务器会向客户端发送保持活动ping。将此选项设置为TimeSpan.MaxValue时,会禁用保持活动ping。
                kestrelServerOptions.Limits.Http2.KeepAlivePingDelay = TimeSpan.FromSeconds(30);

                // 配置ping超时的TimeSpan。如果服务器在此超时期间没有收到任何帧(如响应ping),则连接将关闭。将此选项设置为TimeSpan.MaxValue时,会禁用保持活动状态超时。
                kestrelServerOptions.Limits.Http2.KeepAlivePingTimeout = TimeSpan.FromMinutes(1);
            });
            webBuilder.UseStartup<Startup>();
        });

同步I/O

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(kestrelServerOptions =>
            {
                // 配置Kestrel

                // 控制是否允许对请求和响应使用同步I/O
                kestrelServerOptions.AllowSynchronousIO = true;
            });
            webBuilder.UseStartup<Startup>();
        });

大量阻止同步I/O的操作可能会导致线程池资源不足,进而导致应用无响应。仅在使用不支持异步I/O的库时,才启用AllowSynchronousIO

参考

posted @ 2022-10-11 12:01  TaylorShi  阅读(838)  评论(1编辑  收藏  举报