问题 Server 2008 RC2,IIS 7.5,ASP.NET和请求排队性能不佳


我已经知道了这个问题的答案,但是希望与社区分享,因为它没有来自Microsoft的文档。

场景:大量的流量袭击了IIS 7.5 ASP.NET网站,您注意到请求开始排队。网站的性能慢下来,但你有足够的CPU和RAM可用。

这是我们最近看到的一个网站,它产生了一堆内部网络服务电话。内部运行状况检查将开始超时,这将导致此服务器退出群集。 (但是,这台服务器是最强大的硬件......)


9117
2018-05-04 20:07


起源



答案:


在互联网上搜索后,我发现微软发布了以下与此问题相关的文章:

KB 821268: 从ASP.NET应用程序发出Web服务请求时出现争用,性能不佳和死锁

这篇文章提供了一些很好的性能调整技巧,但它没有提到我们遇到的一些非常重要的天花板。

我们的解决方案是修改我们的machine.config,并填充以下XML节点:

<system.web>
    <processModel autoConfig="false" maxWorkerThreads="xxx" maxIoThreads="xxx" minWorkerThreads="xxx" minIoThreads="xxx" requestQueueLimit="5000" responseDeadlockInterval="00:03:00"/>
    <httpRuntime minFreeThreads="xxx" minLocalRequestFreeThreads="xxx"/>
</system.web>

我故意将其中一些数字设置为“xxx”,因为它们取决于您的硬件。

从上面的KB文章中,Microsoft建议了一些用于计算这些值的方程式。但是,他们没有提到这些数字的MAXIMUM值是INT的大小,即32767。

所以 正确 用于计算这些的公式如下:

  • maxWorkerThreads:32767 / #Cores
    • 在我们的例子中,我们有一个24核服务器。因此,我们的maxWorkerThreads值正确设置为:1365。任何导致整数LARGER超过32767的数字,服务器都会将maxWorkerThreads设置为32767。
  • maxIoThreads:与maxWorkerThreads相同(32767 / #Cores)
  • minWorkerThreads:maxWorkerThreads / 2
    • 这是一个棘手的问题。如果一个超过整数值LARGER而不是32767(尽管知识库文章说的是这个数字乘以你拥有的核心数),并且与“max”值不同, 这默认为计算机上的核心数!在我们的例子中,这被设置为24(因为我们为min设置了一个任意高的值),这就是杀死我们服务器上的性能。
  • minIoThreads:与minWorkerThreads相同
  • minFreeThreads:88 * #Cores(直接取自知识库文章)
  • minLocalRequestFreeThreads:76 * #Cores(直接取自KB文章)

此解决方案并非适合所有人,只应在符合知识库文章中的条件时使用。

我们用来帮助​​我们诊断的另一个工具是.ASPX页面,没有代码隐藏,我们可以在任何服务器上丢弃(不重置应用程序池)。此页面使用反射来告诉您线程池中实际发生了什么,以及这些设置的值在服务器上呈现的内容。

<%@ Page Language="C#" %>

<!DOCTYPE html>
<html lang="en">
<head>
<style>
    body { margin: 20pt; padding: 0pt; font-family: Verdana, "san-serif";}
    fieldset { border-radius: 5px; border: none; background-color: #fff; margin: 10pt;}
    fieldset.parent { background-color: #f0f0f0; }
    legend { font-size: 10pt; color: #888; margin: 5pt; }

    .ports div { padding: 10pt 0pt 0pt 0pt; clear: both; }
    .ports div:first-child { padding: 0pt; }
    .ports div div { padding: 0pt; clear: none; margin: 1pt; background-color: #eef; display: block; float: left; border: 5pt solid #eef; }
    .ports div div:first-child { border-top-left-radius: 5pt; border-bottom-left-radius: 5pt; background-color: #ccf; border-color: #ccf;}
    .ports div div:last-child { border-top-right-radius: 5pt; border-bottom-right-radius: 5pt; background-color: #ccf; border-color: #ccf; padding: 0pt 10pt 0pt 10pt; }
</style>

</head>
<body>

<%
Response.Cache.SetCacheability(HttpCacheability.NoCache);

int worker, workerMIN, workerMAX;
int port, portMIN, portMAX;
System.Threading.ThreadPool.GetAvailableThreads(out worker, out port);
System.Threading.ThreadPool.GetMinThreads(out workerMIN, out portMIN);
System.Threading.ThreadPool.GetMaxThreads(out workerMAX, out portMAX);

 %>

<fieldset class="parent">
<legend>Thread Information</legend>

<fieldset>
    <legend>Worker Threads</legend>
    <div class="ports">
        <div>
            <div>Min: <%=workerMIN %></div>
            <div>Current: <%=workerMAX - worker %></div>
            <div>Max: <%=workerMAX %></div>
        </div>
    </div>
</fieldset>

<fieldset>
    <legend>Completion Port Threads</legend>
    <div class="ports">
        <div>
            <div>Min: <%=portMIN %></div>
            <div>Current: <%=portMAX - port %></div>
            <div>Max: <%=portMAX %></div>
        </div>
    </div>
</fieldset>

<fieldset>
    <legend>Request Queue Information</legend>
    <div class="ports">

<%


var fi = typeof(HttpRuntime).GetField("_theRuntime", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static).GetValue(null);
var rq = typeof(HttpRuntime).GetField("_requestQueue", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(fi);
var fields = rq.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

foreach (var field in fields)
{
    string name = field.Name;
    string value = "";

    switch (name)
    {
        case "_localQueue":
        case "_externQueue":
            System.Collections.Queue queue = field.GetValue(rq) as System.Collections.Queue;
            value = queue.Count.ToString();
            break;

        default:
            value = field.GetValue(rq).ToString();
            break;
    }

    %>
        <div>
            <div><%=name %></div>
            <div><%=value %></div>
        </div>
    <%
    //Response.Write(string.Format("{0}={1}<br/>", name, value));
}   

%>
    </div>
</fieldset>
</fieldset>



</body></html>

12
2018-05-07 15:31



为什么maxWorkerThreads与#Cores成反比?即你拥有的maxWorkerThreads核心越多。对我来说更有意义的是,maxWorkerThreads是x * #Cores,比如maxWorkerThreads = 16 * #Cores。 - DavidF
你有多确定minWorkerThreads和minIoThreads乘以核心数?这是专门为.net 4.5吗? - Dave Lawrence
看似确认在这里 - msdn.microsoft.com/en-us/library/... - Dave Lawrence


答案:


在互联网上搜索后,我发现微软发布了以下与此问题相关的文章:

KB 821268: 从ASP.NET应用程序发出Web服务请求时出现争用,性能不佳和死锁

这篇文章提供了一些很好的性能调整技巧,但它没有提到我们遇到的一些非常重要的天花板。

我们的解决方案是修改我们的machine.config,并填充以下XML节点:

<system.web>
    <processModel autoConfig="false" maxWorkerThreads="xxx" maxIoThreads="xxx" minWorkerThreads="xxx" minIoThreads="xxx" requestQueueLimit="5000" responseDeadlockInterval="00:03:00"/>
    <httpRuntime minFreeThreads="xxx" minLocalRequestFreeThreads="xxx"/>
</system.web>

我故意将其中一些数字设置为“xxx”,因为它们取决于您的硬件。

从上面的KB文章中,Microsoft建议了一些用于计算这些值的方程式。但是,他们没有提到这些数字的MAXIMUM值是INT的大小,即32767。

所以 正确 用于计算这些的公式如下:

  • maxWorkerThreads:32767 / #Cores
    • 在我们的例子中,我们有一个24核服务器。因此,我们的maxWorkerThreads值正确设置为:1365。任何导致整数LARGER超过32767的数字,服务器都会将maxWorkerThreads设置为32767。
  • maxIoThreads:与maxWorkerThreads相同(32767 / #Cores)
  • minWorkerThreads:maxWorkerThreads / 2
    • 这是一个棘手的问题。如果一个超过整数值LARGER而不是32767(尽管知识库文章说的是这个数字乘以你拥有的核心数),并且与“max”值不同, 这默认为计算机上的核心数!在我们的例子中,这被设置为24(因为我们为min设置了一个任意高的值),这就是杀死我们服务器上的性能。
  • minIoThreads:与minWorkerThreads相同
  • minFreeThreads:88 * #Cores(直接取自知识库文章)
  • minLocalRequestFreeThreads:76 * #Cores(直接取自KB文章)

此解决方案并非适合所有人,只应在符合知识库文章中的条件时使用。

我们用来帮助​​我们诊断的另一个工具是.ASPX页面,没有代码隐藏,我们可以在任何服务器上丢弃(不重置应用程序池)。此页面使用反射来告诉您线程池中实际发生了什么,以及这些设置的值在服务器上呈现的内容。

<%@ Page Language="C#" %>

<!DOCTYPE html>
<html lang="en">
<head>
<style>
    body { margin: 20pt; padding: 0pt; font-family: Verdana, "san-serif";}
    fieldset { border-radius: 5px; border: none; background-color: #fff; margin: 10pt;}
    fieldset.parent { background-color: #f0f0f0; }
    legend { font-size: 10pt; color: #888; margin: 5pt; }

    .ports div { padding: 10pt 0pt 0pt 0pt; clear: both; }
    .ports div:first-child { padding: 0pt; }
    .ports div div { padding: 0pt; clear: none; margin: 1pt; background-color: #eef; display: block; float: left; border: 5pt solid #eef; }
    .ports div div:first-child { border-top-left-radius: 5pt; border-bottom-left-radius: 5pt; background-color: #ccf; border-color: #ccf;}
    .ports div div:last-child { border-top-right-radius: 5pt; border-bottom-right-radius: 5pt; background-color: #ccf; border-color: #ccf; padding: 0pt 10pt 0pt 10pt; }
</style>

</head>
<body>

<%
Response.Cache.SetCacheability(HttpCacheability.NoCache);

int worker, workerMIN, workerMAX;
int port, portMIN, portMAX;
System.Threading.ThreadPool.GetAvailableThreads(out worker, out port);
System.Threading.ThreadPool.GetMinThreads(out workerMIN, out portMIN);
System.Threading.ThreadPool.GetMaxThreads(out workerMAX, out portMAX);

 %>

<fieldset class="parent">
<legend>Thread Information</legend>

<fieldset>
    <legend>Worker Threads</legend>
    <div class="ports">
        <div>
            <div>Min: <%=workerMIN %></div>
            <div>Current: <%=workerMAX - worker %></div>
            <div>Max: <%=workerMAX %></div>
        </div>
    </div>
</fieldset>

<fieldset>
    <legend>Completion Port Threads</legend>
    <div class="ports">
        <div>
            <div>Min: <%=portMIN %></div>
            <div>Current: <%=portMAX - port %></div>
            <div>Max: <%=portMAX %></div>
        </div>
    </div>
</fieldset>

<fieldset>
    <legend>Request Queue Information</legend>
    <div class="ports">

<%


var fi = typeof(HttpRuntime).GetField("_theRuntime", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static).GetValue(null);
var rq = typeof(HttpRuntime).GetField("_requestQueue", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(fi);
var fields = rq.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

foreach (var field in fields)
{
    string name = field.Name;
    string value = "";

    switch (name)
    {
        case "_localQueue":
        case "_externQueue":
            System.Collections.Queue queue = field.GetValue(rq) as System.Collections.Queue;
            value = queue.Count.ToString();
            break;

        default:
            value = field.GetValue(rq).ToString();
            break;
    }

    %>
        <div>
            <div><%=name %></div>
            <div><%=value %></div>
        </div>
    <%
    //Response.Write(string.Format("{0}={1}<br/>", name, value));
}   

%>
    </div>
</fieldset>
</fieldset>



</body></html>

12
2018-05-07 15:31



为什么maxWorkerThreads与#Cores成反比?即你拥有的maxWorkerThreads核心越多。对我来说更有意义的是,maxWorkerThreads是x * #Cores,比如maxWorkerThreads = 16 * #Cores。 - DavidF
你有多确定minWorkerThreads和minIoThreads乘以核心数?这是专门为.net 4.5吗? - Dave Lawrence
看似确认在这里 - msdn.microsoft.com/en-us/library/... - Dave Lawrence