无法将IIS/Asp.NET配置为同时处理多个异步请求

无法将IIS/Asp.NET配置为同时处理多个异步请求,asp.net,.net,iis,asynchronous,concurrency,Asp.net,.net,Iis,Asynchronous,Concurrency,我正在尝试探索异步ASP.NET请求。Asp.NET应用程序由IIS 8托管 客户端正在使用以下代码发出许多POST请求: private static async Task<Result> IOAsync(Uri url, byte[] body) { var webClient = new WebClient(); webClient.Headers["Content-Type"] = "application/json"; return Deserial

我正在尝试探索异步ASP.NET请求。Asp.NET应用程序由IIS 8托管

客户端正在使用以下代码发出许多POST请求:

private static async Task<Result> IOAsync(Uri url, byte[] body)
{
    var webClient = new WebClient();
    webClient.Headers["Content-Type"] = "application/json";
    return DeserializeFromBytes(await webClient.UploadDataTaskAsync(url, "POST", body));
}

private static Result DeserializeFromBytes(byte[] bytes)
{
    using (var jsonTextReader = new JsonTextReader(new StreamReader(new MemoryStream(bytes))))
    {
        return new JsonSerializer().Deserialize<Result>(jsonTextReader);
    }
}
sampleapiaasync
旨在探索实现数据库IO的不同方法,其中只有一种是真正异步的,其余的则是为了证明关于使用
Task.Run
和类似方法“模拟”数据库IO的常见误解

在我的特定场景中,
IOAsync
方法是真正异步的:

private async Task<Result> IOAsync(string sqlPauseDuration)
{
    Result result;
    using (var conn = new SqlConnection(m_connectionString))
    using (var cmd = CreateCommand(conn, sqlPauseDuration))
    {
        await conn.OpenAsync();
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            await reader.ReadAsync();
            result = new Result(reader.GetDateTime(0), reader.GetGuid(1));
        }
    }
    return result;
}

private SqlCommand CreateCommand(SqlConnection conn, string sqlPauseDuration)
{
    const string SQL = "WAITFOR DELAY @Duration;SELECT GETDATE(),NEWID()";
    var sqlCommand = new SqlCommand(SQL, conn) { CommandTimeout = QueryTimeout };
    sqlCommand.Parameters.Add(new SqlParameter("Duration", sqlPauseDuration));
    return sqlCommand;
}
在sql server上,持续时间为20秒的
@不会产生任何峰值,并且需要5分钟(几乎完全如此)!从中我得出结论,请求的处理并没有足够的并发性。如果我猜的话,我会说它同时处理(5*60)/20=15个请求

请注意:

  • 在进行测试之前,我有“冷运行”请求来预热IIS和Sql Server。因此,启动时间对方程没有影响
  • 当我使用相同的设置直接从客户端运行
    IOAsync
    时——600个请求加20秒,我确实看到了Sql Server上线程计数的预期峰值,并且所有600个请求在不到21秒的时间内同时完成
由此我得出结论,问题出在Asp.NET/IIS方面

通过谷歌搜索该问题,提示我更改文件C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Aspnet.configC:\Windows\Microsoft.NET\Framework\v4.0.30319\Aspnet.config,如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <runtime>
        <legacyUnhandledExceptionPolicy enabled="false" />
        <legacyImpersonationPolicy enabled="true"/>
        <alwaysFlowImpersonationPolicy enabled="false"/>
        <SymbolReadingPolicy enabled="1" />
        <shadowCopyVerifyByTimestamp enabled="true"/>
    </runtime>
    <startup useLegacyV2RuntimeActivationPolicy="true" />
    <system.web> 
        <applicationPool maxConcurrentRequestsPerCPU="5000" maxConcurrentThreadsPerCPU="0" requestQueueLimit="5000"/> 
    </system.web>
</configuration>
响应

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 19 Nov 2015 17:37:15 GMT
Content-Length: 83

{"Id":"16e8c3a2-fc95-446a-9459-7a89f368e074","Timestamp":"\/Date(1447954635240)\/"}
编辑2

请在下面找到Asp.Net应用程序的web.config:

<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0"/>
    <add key="webpages:Enabled" value="false"/>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
  </appSettings>
  <!--
    For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

    The following attributes can be set on the <httpRuntime> tag.
      <system.Web>
        <httpRuntime targetFramework="4.5.2" />
      </system.Web>
  -->
  <system.web>
    <compilation debug="true" targetFramework="4.5.2"/>
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed"/>
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
这与

现在,我已经部署到Windows Server 2012上,在那里我可以发出75个DB请求,每个请求长10秒,几乎总共10秒完成。但不是76,我从中得出结论,实际并发限制是75。不是5000。仍在寻找线索

编辑6

根据的建议,我已将所有DB IO替换为
Task.Delay
,并停止Sql Server

如果没有asp.net,我可以轻松运行600
任务。延迟10秒,所有任务都会在10秒内结束(只需要一点点额外的时间)


在asp.net中,结果是一致的——75个请求是完全并发和异步的。除此之外,情况有所不同。因此,80个请求耗时16秒,100个请求耗时20秒,200个请求耗时30秒。对我来说,很明显,请求是由Asp.NET或IIS限制的,就像以前执行DB IO时一样。

我猜这是由于客户端“每个端点最多两个并发连接”规则造成的,这是由原始HTTP规范强制执行的

您需要设置适当的值以允许更多并发连接

例如:(在发送请求之前在客户端执行此操作)

这将允许您的应用程序打开无限(实际上)数量的并发HTTP连接


以下引述自:

单用户客户端与任何服务器或代理的连接不应超过2个[…]这些准则旨在提高HTTP响应时间并避免拥塞

因此,兼容客户端通常必须确保其与单个服务器的开放连接不会超过两个

但是,这已经改变,您可以覆盖此行为。以下引用是对HTTP/1.1协议的更新:

以前的HTTP版本将特定数量的连接作为 上限,但对于许多应用来说,这是不切实际的。 因此,本规范未规定特定的最大值 连接数,但鼓励客户端 在打开多个连接时,请保守


在一天结束时,在遵循给出的建议后,我找到了罪犯。令人惊讶的是,是Fiddler阻止了这些请求!我想有一个小提琴手在某处控制着它


我通过检查请求统计信息中的
ClientConnected
vs
ClientBeginRequest
时间戳发现了它。虽然
ClientConnected
是相同的-当客户端发送请求时,它有不同的
ClientBeginRequest
-在一定数量的请求之后,它开始延迟,延迟逐渐增加。

如果您在Windows 8上而不是在Server 2012上运行IIS 8.0,有些东西有硬编码的限制。您应该能够将其增加到默认值以上(Windows 8 Professional的默认值限制为10,Basic edition的默认值限制为3):‌​.

因此,在applicationHost.config的
部分(通常位于C:\Windows\System32\inetsrv\config中),您可以添加
元素,如本文末尾所示:

<sites>
    <site name="Default Web Site" id="1" serverAutoStart="true">
        <application path="/">
            <virtualDirectory path="/" physicalPath="C:\inetpub\wwwroot" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:80:" />
        </bindings>
    </site>
     <site name="X" id="2">
        <application path="/" applicationPool="X">
            <virtualDirectory path="/" physicalPath="C:\Inetpub\wwwroot\X" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:80:X" />
        </bindings>
        <traceFailedRequestsLogging enabled="false" />
        <limits maxConnections="40" />
    </site>
    <siteDefaults>
        <logFile logFormat="W3C" directory="%SystemDrive%\inetpub\logs\LogFiles" />
        <traceFailedRequestsLogging directory="%SystemDrive%\inetpub\logs\FailedReqLogFiles" />
    </siteDefaults>
    <applicationDefaults applicationPool="DefaultAppPool" />
    <virtualDirectoryDefaults allowSubDirConfig="true" />
</sites>

最大可用值为40

使用IIS管理器选择网站,然后更改“高级设置…”->“限制”->“最大并发连接数”中的值可能更安全


如果您需要更多并发连接,一个选项是获得Windows Server 2012的试用版,并在虚拟机(Hyper-V)中运行。我建议将与开发平台相对应的服务器版本(如server 2012 R2)与Windows 8.1配合使用,这样您就可以在这两个平台上发现相同的问题。

ASP.NET会话状态如何?我不知道如何检查它。添加
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 19 Nov 2015 17:37:15 GMT
Content-Length: 83

{"Id":"16e8c3a2-fc95-446a-9459-7a89f368e074","Timestamp":"\/Date(1447954635240)\/"}
<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0"/>
    <add key="webpages:Enabled" value="false"/>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
  </appSettings>
  <!--
    For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

    The following attributes can be set on the <httpRuntime> tag.
      <system.Web>
        <httpRuntime targetFramework="4.5.2" />
      </system.Web>
  -->
  <system.web>
    <compilation debug="true" targetFramework="4.5.2"/>
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed"/>
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
<system.web>
  <compilation debug="true" targetFramework="4.5.2"/>
  <httpRuntime targetFramework="4.5"/>
  <sessionState mode="Off" />
</system.web>
PS C:\> &"C:\Program Files\IIS Express\appcmd.exe" list config -section:system.applicationHost/sites |sls limits | group

Count Name                      Group
----- ----                      -----
   30       <limits />          {      <limits />,       <limits />,       <limits />,       <limits />...}


PS C:\>
NumberOfConcurrentRequests = TotalRequests / NumberOfBatches 
                           = TotalRequests / (TotalTime / OneRequestDuration)
                           = (TotalRequests / TotalTime) * OneRequestDuration
                           = (600 / 300) * 5
                           = 10
ServicePointManager.DefaultConnectionLimit = int.MaxValue;
<sites>
    <site name="Default Web Site" id="1" serverAutoStart="true">
        <application path="/">
            <virtualDirectory path="/" physicalPath="C:\inetpub\wwwroot" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:80:" />
        </bindings>
    </site>
     <site name="X" id="2">
        <application path="/" applicationPool="X">
            <virtualDirectory path="/" physicalPath="C:\Inetpub\wwwroot\X" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:80:X" />
        </bindings>
        <traceFailedRequestsLogging enabled="false" />
        <limits maxConnections="40" />
    </site>
    <siteDefaults>
        <logFile logFormat="W3C" directory="%SystemDrive%\inetpub\logs\LogFiles" />
        <traceFailedRequestsLogging directory="%SystemDrive%\inetpub\logs\FailedReqLogFiles" />
    </siteDefaults>
    <applicationDefaults applicationPool="DefaultAppPool" />
    <virtualDirectoryDefaults allowSubDirConfig="true" />
</sites>