ASP.NET 性能监视和何时向管理员发出警报

 

Thomas Marquardt
Microsoft Corporation

更新时间:2003 年 7 月

适用于:
   Microsoft® ASP.NET

总结: 讨论哪些性能计数器最有助于诊断 Microsoft ASP.NET 应用程序中的压力和性能问题、应设置哪些阈值以提醒管理员注意问题,以及可用于监视 ASP.NET 应用程序运行状况的其他资源。 (17 页)

下载本文的源代码

Contents

监视性能计数器
监视事件日志
监视 W3C 和 HTTPERR 日志
用于监视 ASP.NET 的其他资源
了解性能计数器
   .NET CLR 异常计数器
   .NET CLR 加载计数器
   .NET CLR 内存计数器
   ASP.NET 计数器
   ASP.NET 应用程序计数器
   进程计数器
   处理器计数器
   内存计数器
   系统计数器
   Web 服务计数器
结论

监视性能计数器

有许多性能计数器可用于监视应用程序。 选择要包含在性能日志中的日志可能比较棘手,了解如何解释它们是一门艺术。 本文可帮助你更熟悉这两项任务。

至少应针对 Microsoft® ASP.NET 应用程序监视以下性能计数器:

  • Processor(_Total)\% Processor Time
  • 处理 (aspnet_wp) \% 处理器时间
  • 处理 (aspnet_wp) \专用字节数
  • 处理 (aspnet_wp) \虚拟字节数
  • 进程 (aspnet_wp) \句柄计数
  • Microsoft® .NET CLR Exceptions\# Exceps thrown/ sec
  • ASP.NET\应用程序重启
  • ASP.NET\Requests Rejected
  • ASP.NET\工作进程重启 (不适用于 IIS 6.0)
  • 内存\可用兆字节数
  • Web 服务\当前连接
  • Web 服务\ISAPI 扩展请求数/秒

下面是一个较大的性能计数器列表,这些计数器可用于监视性能。 拥有更多性能数据始终是好事,尤其是在遇到不容易重现的问题时。 该列表省略了一些通常不需要的性能计数器。 例如,仅当使用功能时,才需要会话状态和事务性能计数器。

根据我调试和测试 ASP.NET 应用程序的经验,建议使用一些阈值。 可以在本文中搜索“阈值”以直接跳转到它们。 管理员应根据经验确定是否在超过这些阈值时发出警报。 在大多数情况下,警报是适当的,尤其是在长时间超过阈值时。

监视事件日志

监视来自 ASP.NET 和 Microsoft® Internet Information Server (IIS) 的消息的事件日志至关重要。 ASP.NET 将消息写入应用程序日志,例如,每次aspnet_wp工作进程终止时。 IIS 6.0 将消息写入应用程序和/或系统日志,例如,每次 w3wp 工作进程报告自身不正常或崩溃时。 编写 .NET 应用程序很容易读取应用程序日志并筛选出来自 ASP.NET 和 IIS 的消息,并在必要时发送电子邮件或拨打) 寻呼机 (触发警报。

监视 W3C 和 HTTPERR 日志

首先,通过 Internet Information Services (IIS) 管理器为 IIS 5.0 和 IIS 6.0 启用 W3C 日志记录。 可以将此日志配置为包含有关请求的各种数据,例如 URI、状态代码等。 扫描日志中的错误代码(如 404 未找到),并在必要时采取措施更正链接。 在 IIS 6.0 上,子状态代码包含在日志中,可用于调试。 IIS 使用子状态代码来标识特定问题。 例如,404.2 指示处理请求的 ISAPI 扩展已锁定。 可以在 关于自定义错误消息 主题中找到状态和子状态代码的列表。

IIS 6.0 的新增功能、格式错误或错误请求以及应用程序池无法提供服务的请求通过HTTP.SYS(用于处理 HTTP 请求的内核模式驱动程序)记录到 HTTPERR 日志中。 每个条目都包含 URL 和错误的简要说明。

检查 HTTPERR 日志中是否存在拒绝的请求。 当超出内核请求队列时以及应用程序被快速故障保护功能脱机时,HTTP.SYS会拒绝请求。 当第一个问题发生时,URL 记录在 消息 QueueFull 中,当第二个问题发生时,该消息为 AppOffline。 默认情况下,内核请求队列设置为 1,000,并且可以在 IIS 管理器的“应用程序池属性”页上进行配置。 建议将繁忙站点的此值增加到 5,000,因为如果应用程序池在站点负载非常高时崩溃,内核请求队列很容易超过 1,000。

检查 HTTPERR 日志中是否存在因工作进程崩溃或挂起而丢失的请求。 发生这种情况时,将为每个未完成的请求记录 URL,其中包含消息 (Connection_Abandoned_By_AppPool)。 未完成的请求是已发送到工作进程进行处理,但在崩溃或挂起之前未完成的请求。

有关 HTTPERR 日志的详细信息,请参阅 Microsoft 知识库文章 820729: INFO:HTTP API 中的错误日志记录

用于监视 ASP.NET 的其他资源

性能计数器和事件日志不会捕获发生的所有错误,因此不足以监视 ASP.NET。 我建议编写一个简单的应用程序,该应用程序发送一个或多个页面的 HTTP 请求,并期望得到特定的响应。 此工具应监视上一个字节 (TTLB) 的时间,以确保及时提供页面。 它还应记录发生的任何错误,因为需要此信息来分析问题。

IIS 6.0 资源工具包包括 日志分析程序 2.1,该工具用于分析日志文件 (W3C 日志、HTTPERR 日志、事件日志) 并将结果存储在文件或数据库中。 资源工具包可以安装在 Microsoft® Windows® XP 和 Microsoft® Windows Server™ 2003 上。

还可以编写一个应用程序,用于收集性能数据、筛选事件日志,并在 Microsoft® SQL Server 数据库中记录关键数据。 使用 System.Diagnostics 命名空间执行此操作非常简单。 甚至可以使用 System.Diagnostics.Process 类监视工作进程重启。

为了帮助你入门,请使用本文顶部的链接下载几个有用工具的示例代码:

  1. snap.exe的源代码,这是一种用于记录进程性能数据的命令行工具。 Snap.cs 文件包含简要说明并说明了如何编译该工具。
  2. HttpClient.exe 的源代码,这是一个简单的客户端,用于记录 HTTP 请求的上一个字节 (TTLB) 的时间。 HttpClient.cs 文件包含简要说明,并说明了如何编译该工具。
  3. qqq.exe的源代码,这是一种用于对 ASP.NET 应用程序进行压力测试的命令行工具。 与压力客户端(如 Microsoft® Application Center Test (ACT) )结合使用时,此工具会将调试器附加到工作进程并监视某些性能计数器。 可以对其进行优化,以在性能下降时中断调试器。 文件 qqq.cs 包含一个 breif 说明,并说明了如何编译该工具。
  4. pminfo.aspx 页使用 System.Web.ProcessModelInfo 类显示有关aspnet_wp进程重启的信息。 历史记录将一直保留到 w3svc 服务停止为止。
  5. ErrorHandler.dll的源代码。 这是一个 IHttpModule,可以添加到 HTTP 管道,以将未经处理的异常记录到事件日志中。 最好将错误记录到SQL Server数据库,但为了简单起见,此示例使用事件日志。

另一个简单的步骤是实现 Application_Error。 可以将以下文本添加到 global.asax,并立即开始将大多数未经处理的错误记录到应用程序日志:

<%@ import namespace="System.Diagnostics" %>
<%@ import namespace="System.Text" %>

const string sourceName      = ".NET Runtime";
const string serverName      = ".";
const string logName         = "Application";
const string uriFormat       = "\r\n\r\nURI: {0}\r\n\r\n";
const string exceptionFormat = "{0}: \"{1}\"\r\n{2}\r\n\r\n";

void Application_Error(Object sender, EventArgs ea) {
    StringBuilder message = new StringBuilder();
    
    if (Request != null) {
        message.AppendFormat(uriFormat, Request.Path);
    }
  
    if (Server != null) {
        Exception e;
        for (e = Server.GetLastError(); e != null; e = e.InnerException) {
            message.AppendFormat(exceptionFormat, 
                                 e.GetType().Name, 
                                 e.Message,
                                 e.StackTrace);
        }
    }

    if (!EventLog.SourceExists(sourceName)) {
        EventLog.CreateEventSource(sourceName, logName);
    }

    EventLog Log = new EventLog(logName, serverName, sourceName);
    Log.WriteEntry(message.ToString(), EventLogEntryType.Error);

    //Server.ClearError(); // uncomment this to cancel the error
}

Application_Error 将捕获页中的分析程序、编译和运行时错误。 它不会捕获配置问题,也不会在处理请求时捕获 inetinfo 中发生的错误aspnet_isapi。 此外,使用模拟时,模拟令牌必须具有写入此事件源的权限。 可以通过将错误记录到SQL Server数据库来避免模拟问题。

最后但并非最不重要的一点,适用于 Windows 的 Microsoft® 调试工具对于调试生产 Web 服务器上的问题非常有用。 可以从 下载 https://www.microsoft.com/whdc/ddk/debugging/installx86.mspx这些工具。 有一个名为 sos.dll 的调试器扩展,你可以将其加载到调试器windbg.exe或cdb.exe来调试托管代码。 它可以将垃圾回收的内容转储 (GC) 堆、显示托管堆栈跟踪、帮助调查托管锁的争用、显示线程池统计信息等。 这可以下载为 .NET Framework 应用程序的生产调试中提到的调试工具集的一部分。

了解性能计数器

下面是重要性能计数器及其用法的简要说明。

.NET CLR 异常计数器

_Global_ 计数器实例不应与此计数器一起使用,因为它由所有托管进程更新。 请改用 aspnet_wp 实例。

  • #Exceps引发/秒。每秒引发的托管异常总数。 随着此数字的增加,性能会下降。 在正常处理过程中,不应引发异常。 但请注意, Response.RedirectServer.TransferResponse.End 都会导致 ThreadAbortException 多次引发,严重依赖于这些方法的站点将产生性能损失。 如果必须使用 Response.Redirect,请调用 Response.Redirect (url,false) ,它不调用 Response.End,因此不会引发。 缺点是调用 Response.Redirect (url 后的用户代码将执行 false) 。 也可以使用静态 HTML 页面进行重定向。 Microsoft 知识库文章312629 提供了更多详细信息。

    除了监视此非常有用的性能计数器外,还应使用 Application_Error 事件来提醒管理员出现问题。

    阈值:RPS 的 5%。 应调查大于此值的值,并根据需要设置新的阈值。

.NET CLR 加载计数器

_Global_ 计数器实例不应与这些性能计数器一起使用,因为它由所有托管进程更新。 请改用 aspnet_wp 实例。

  • 当前 AppDomains。 进程中加载的当前 AppDomain 数。 此计数器的值应与 Web 应用程序数加 1 相同。 其他 AppDomain 是默认域。

  • 当前程序集。 进程中加载的当前程序集数。 默认情况下,目录中的 ASPX 和 ASCX 文件是“批处理”编译的。 这通常会生成一到三个程序集,具体取决于依赖项。 例如,如果 ASPX 页面在 ASCX 文件上具有分析时间依赖项,通常会生成两个程序集。 一个将包含 ASPX 文件,另一个 ASCX 文件。 分析时间依赖项包括由 <%@ import %>、 <%@ reference %>和 <%@ register %> 指令创建的依赖项。 通过 LoadControl 方法加载的控件不会创建分析时依赖项。 请注意,global.asax 本身编译为程序集。

    有时,过多的内存消耗是由异常大量的已加载程序集引起的。 例如,显示新闻文章的网站使用从数据库获取新闻的一小部分 ASPX 文件比用于每篇文章的单个 ASPX 文件性能更好。 网站设计人员应尝试动态生成内容、使用缓存并减少 ASPX 和 ASCX 页面的数量。

    无法从 AppDomain 卸载程序集。 为防止内存消耗过多,当 ASPX、ASCX、ASAX) (的重新编译数超过编译 numRecompilesBeforeAppRestart=/>指定的<限制时,将卸载 AppDomain。 请注意,如果 <%@ 页 debug=%> 属性设置为 true,或者如果 <compilation debug=/> 设置为 true,则会禁用批处理编译。

  • 加载程序堆中的字节数。 类加载程序跨所有 AppDomain 提交的字节数。 此计数器应达到稳定状态。 如果此计数器在不断增加,请监视“当前程序集”计数器。 每个 AppDomain 加载的程序集可能太多。

.NET CLR 内存计数器

_Global_ 计数器实例不应与这些性能计数器一起使用,因为它由所有托管进程更新。 请改用 aspnet_wp 实例。

  • # 所有堆中的字节数。 托管对象提交的字节数。 这是大型对象堆与第 0、1 和 2 代堆的总和。 这些内存区域的类型为 MEM_COMMIT (请参阅 VirtualAlloc) 的平台 SDK 文档。 此计数器的值将始终小于 Process\Private Bytes 的值,后者对进程的所有MEM_COMMIT区域进行计数。 所有堆中的专用字节减去 # 字节数是非托管对象提交的字节数。 调查过多内存使用情况的第一步是确定托管对象还是非托管对象使用它。

    若要调查过多的托管内存使用量,建议WINDBG.EXE和SOS.DLL,可以在 .NET Framework 应用程序的生产调试中了解这些内容。 SOS.DLL具有“!dumpheap –stat”命令,用于列出托管堆中对象的计数、大小和类型。 可以使用“!dumpheap –mt”获取对象的地址,使用“!gcroot”查看其根。 命令“!eeheap”提供托管堆的内存使用情况统计信息。

    用于诊断内存使用情况的另一个有用工具是 CLR Profiler,下面将更详细地讨论。

    过多的托管内存使用量通常由以下因素造成:

    1. 将大型数据集读入内存中。
    2. 创建过多的缓存条目。
    3. 上载或下载大文件。
    4. 在分析文件时过多地使用正则表达式或字符串。
    5. ViewState 过多。
    6. 会话状态中有过多的数据或者会话过多。
  • # 第 0 代集合。 第 0 代对象被垃圾回收的次数。 幸存的对象将提升到第 1 代。 当需要房间来分配对象时,或者当某人通过调用 System.GC.Collect 强制集合时,将执行集合。 涉及较高代系的集合需要更长的时间,因为它们前面是较低代的集合。 尝试最小化第 2 代集合的百分比。 按经验法则,第 0 代集合的数量应比第 1 代集合的数量大 10 倍,第 1 代和第 2 代的集合数应与此类似。 # Gen N 集合计数器和 GC 计数器中的时间百分比最适合用于识别过度分配导致的性能问题。 有关提高性能可以采取的步骤,请参阅 GC 中时间百分比的说明。

  • # 第 1 代集合。 第 1 代对象被垃圾回收的次数。 幸存的对象将提升到第 2 代。

    阈值:#Gen 0 集合值的十分之一。 性能良好的应用程序遵循 #Gen 0 Collections 计数器的说明中提到的经验法则。

  • # 第 2 代集合。 第 2 代对象被垃圾回收的次数。 第 2 代是最高的,因此在集合中幸存的对象仍保留在第 2 代。 第 2 代集合可能非常昂贵,尤其是在第 2 代堆的大小过大时。

    阈值:#Gen 1 集合值的十分之一。 性能良好的应用程序遵循 #Gen 0 Collections 计数器的说明中提到的经验法则。

  • GC 所占时间百分比。 执行上次垃圾回收所花费的时间百分比。 平均值为 5% 或更低会被视为正常值,但大于此值的峰值并不罕见。 请注意,所有线程在垃圾回收期间都挂起。

    GC 中时间百分比过高的最常见原因是每个请求的分配过多。 第二个最常见的原因是将大量数据插入到 ASP.NET 缓存中,将其删除、重新生成,然后每隔几分钟将其重新插入缓存中。 通常可以进行一些小更改,以大大减少分配。 例如,每个请求的字符串串联成本可能很高,因为需要对串联的字符串进行垃圾回收。 具有适当初始容量的 StringBuilder 的性能优于字符串串联。 但是,StringBuilder 也需要进行垃圾回收,如果使用不当,在调整内部缓冲区的大小时,可能会导致分配量超出预期。 对每个字符串多次调用 Response.Write 的性能甚至优于将它们与 StringBuilder 组合在一起,因此,如果可以避免使用 StringBuilder altogther,请执行此操作。

    应用程序通常会在生成响应时暂时将数据存储在 StringBuilder 或 MemoryStream 中。 请考虑实现字符或字节数组的可重用缓冲池,而不是在每个请求上重新创建此临时存储。 缓冲池是具有 GetBufferReturnBuffer 例程的对象。 GetBuffer 例程尝试从内部缓冲区存储中返回缓冲区,但如果存储为空,则会创建一个新缓冲区。 如果尚未达到存储缓冲区的最大数目, ReturnBuffer 例程会将缓冲区返回到存储区,但会释放该缓冲区。 此缓冲池实现的缺点是需要锁定才能确保线程安全。 或者,可以使用 HttpContext.ApplicationInstance 访问 global.asax 中定义的实例字段,从而避免锁定对性能的影响。 每个管道实例都有一个 global.asax 实例,因此一次只能从一个请求访问该字段,使其成为存储可重用字符或字节缓冲区的好地方。

    若要减少 GC 中的时间百分比,需要知道分配模式。 使用 CLR Profiler 分析单个请求或轻压力,最多分析几分钟。 (这些工具是侵入性的,不应在 producton 中使用。) “分配图”视图显示为每个对象类型分配的总内存,以及执行分配的调用堆栈。 使用此来削减过多的分配。 “按大小划分的直方图”视图 (从“视图”菜单中选择“直方图分配的类型”) 汇总已分配对象的大小。 避免分配大于 85,000 字节的短生存期对象。 这些对象在大型对象堆中分配,收集成本更高。 在“按大小划分的直方图”视图中,可以使用鼠标选择对象,然后右键单击以查看分配对象的人员。 减少分配是代码修改和分析的迭代过程。

    阈值:平均为 5% 或更低;大于此值的短期峰值很常见。 应调查大于此值的平均值。 应根据需要设置新的阈值。

ASP.NET 计数器

此类别中的性能计数器仅在 w3svc 服务启动时重置为 0。

  • 应用程序重启。 应用程序重启的次数。 重新创建应用程序域和重新编译页面需要一段时间,因此应调查不可预见的应用程序重启。 发生以下情况之一时,将卸载应用程序域:

    • 修改machine.configweb.configglobal.asax
    • 修改应用程序的 bin 目录或其内容。
    • 当 ASPX、ASCX 或 ASAX) (编译数超过编译 numRecompilesBeforeAppRestart=/>指定的<限制时。
    • 修改虚拟目录的物理路径。
    • 修改代码访问安全策略。
    • Web 服务已重启。

    对于生产中的 Web 场,建议在更新内容之前从轮换中删除服务器,以便获得最佳性能和可靠性。 对于生产中的单个 Web 服务器,可以在服务器加载时更新内容。 知识库文章810281中所述的修补程序对于在应用程序重启后遇到错误的任何人感兴趣,例如共享冲突并出现类似于“无法访问文件 <FileName>,因为它正由另一个进程使用”的错误。

    知识库文章820746: 修复:某些防病毒程序可能导致 v1.0 的 Web 应用程序意外重启和知识库文章 821438 中针对 v1.1 修复了涉及防病毒软件和应用程序重启的问题。

    阈值:0。 在完美的世界中,应用程序域将在该过程的整个生命周期中幸存下来。 应调查过多值,并根据需要设置新的阈值。

  • 正在运行的应用程序。 正在运行的应用程序数。

  • 请求当前。 ASP.NET ISAPI 当前处理的请求数。 这包括排队、正在执行或等待写入客户端的客户端。 此性能计数器已添加到知识库文章 329959 中描述的 sp3 前修补程序中的 ASP.NET v1.0

    当此计数器超过 processModel 配置部分中定义的 requestQueueLimit 时,ASP.NET 将开始拒绝请求。 请注意,在 aspnet_wp 中运行时,requestQueueLimit 适用于 IIS 5.0 上的 ASP.NET,但也许令人惊讶的是,在 w3wp 中运行时,它也适用于 IIS 6.0。 众所周知,在 IIS 6.0 中运行时,多个 processModel 配置设置仍适用。 其中包括 requestQueueLimit、responseDeadlockInterval、maxWorkerThreads、maxIoThreads、minWorkerThreads 和 minIoThreads。 在 2003 年 6 月 1 日修补程序汇总包 ASP.NET 1.1 中修复的框架 v1.1 中的 bug 允许 ASP.NET 在 IIS 6.0 中运行时处理无限数量的请求。 该修补程序导致当请求当前数超过 requestQueueLimit 时,ASP.NET 拒绝请求。

    对于经典 ASP 应用程序,排队的请求会在何时拒绝请求时提供警告。 对于 ASP.NET,当前请求以及应用程序队列中的请求提供此功能。ASP.NET 死锁检测机制也使用此计数器。 如果“当前请求数”大于 0,并且工作进程在超过 processModel responseDeadlockInterval=/>指定的<限制的持续时间内未收到任何响应,则进程将终止并启动一个新进程。 在知识库文章 328267 中所述的 SP3 前修补程序中,修改了算法,以便“当前请求数”必须大于 processModel/> 配置节中指定的 <maxWorkerThreadsmaxIoThreads 之和。 请注意,默认情况下,请求执行超时为 90 秒,并且有意小于 responseDeadlockInterval。 可以通过 httpRuntime executionTimeout=/> 配置设置或 Server.ScriptTimeout 属性修改<请求执行超时,但应始终小于 responseDeadlockInterval

  • 请求执行时间。 执行最后一个请求所花费的毫秒数。 在 Framework 版本 1.0 中,执行时间从工作进程收到请求时开始,当 ASP.NET ISAPI 将HSE_REQ_DONE_WITH_SESSION发送到 IIS 时停止。 对于 IIS 版本 5,这包括将响应写入客户端所需的时间,但对于 IIS 版本 6,响应缓冲区以异步方式发送,因此不包括将响应写入客户端所需的时间。 因此,在 IIS 版本 5 上,网络连接较慢的客户端将大大增加此计数器的值。

    在 Framework 版本 1.1 中,执行时间从创建请求的 HttpContext 开始,在将响应发送到 IIS 之前停止。 假设用户代码不调用 HttpResponse.Flush,这意味着在将任何字节发送到 IIS 或客户端之前,执行时间会停止。

    ASP.NET\请求执行时间是实例计数器,并且非常不稳定。 另一方面,可以轻松计算 TTLB) 的上一个字节 (时间,并用于计算一段时间内性能的更好估计。 可以通过用托管代码编写的简单 HTTP 客户端来计算 TTLB,也可以使用可用于计算 TTLB 的众多 HTTP 客户端之一,例如 Application Center Test (ACT) 。

    请注意,如果 <compilation debug=/> 设置为 TRUE,则将禁用批处理编译,并且 <将忽略 httpRuntime executionTimeout=/> 配置设置以及对 Server.ScriptTimeout 的 调用。 如果 <processModel responseDeadlockInterval=/> 设置未设置为 Infinite,则可能会导致问题,因为调试页的请求理论上可以永久运行。

    阈值:N.A. 此计数器的值应是稳定的。 体验将有助于为特定站点设置阈值。 启用进程模型后,请求执行时间包括将响应写入客户端所需的时间,因此取决于客户端连接的带宽。

  • 请求已排队。 当前排队的请求数。 在 IIS 5.0 上运行时,inetinfo 和 aspnet_wp 之间有一个队列,每个虚拟目录都有一个队列。 在 IIS 6.0 上运行时,存在一个队列,其中请求从本机代码发布到托管 ThreadPool,每个虚拟目录都有一个队列。 此计数器包括所有队列中的请求。 inetinfo 和 aspnet_wp 之间的队列是一个命名管道,通过该管道将请求从一个进程发送到另一个进程。 如果aspnet_wp进程中缺少可用的 I/O 线程,则此队列中的请求数会增加。 在 IIS 6.0 上,当存在传入请求和工作线程不足时,它会增加。

    请注意,当请求当前计数器超过 <processModel requestQueueLimit=/>时,请求将被拒绝。 许多人认为,当请求排队计数器超过 requestQueueLimit 时会发生这种情况,但情况并非如此。 超过此限制时,将拒绝请求并显示 503 状态代码和消息“服务器太忙”。如果请求因此原因被拒绝,它将永远不会到达托管代码,并且不会通知错误处理程序。 通常,仅当服务器负载非常繁重时才会出现此问题,尽管每小时的“突发”负载也可能导致此问题。 对于异常的突发负载方案,你可能对 知识库文章810259中所述的修补程序感兴趣,该修补程序允许从每个 CPU 的默认值 1 开始增加最小 I/O 线程数。

    每个虚拟目录都有一个队列,它使用该队列来维护工作线程和 I/O 线程的可用性。 如果可用工作线程数或可用 I/O 线程数低于 httpRuntime minFreeThreads=/>指定的<限制,则此队列中的请求数会增加。 当超过 httpRuntime appRequestQueueLimit=/> 指定的<限制时,请求将被拒绝并显示 503 状态代码,并且客户端会收到 HttpException 并显示消息“服务器太忙”。

    此计数器本身不是性能问题的明确指标,也不能用于确定何时拒绝请求。 在 知识库文章329959中,引入了两个新的性能计数器来解决此问题:“当前请求”和“应用程序队列中的请求”。 请参阅这两个计数器的说明,以及拒绝的请求数。

  • 请求被拒绝。 被拒绝的请求数。 超过其中一个队列限制时,请求将被拒绝 (请参阅请求排队) 的说明。 可能会出于多种原因拒绝请求。 后端延迟(例如 SQL 服务器速度缓慢导致的延迟)通常先于管道实例数突然增加,CPU 利用率和请求数/秒降低。由于处理器或内存限制导致请求被拒绝,服务器可能会在负载过大期间不堪重负。

    应用程序的设计可能会导致请求执行时间过长。 例如,批量编译是一项功能,在收到对页面的第一个请求时,目录中的所有页面都编译为单个程序集。 如果目录中有几百页,则进入此目录的第一个请求可能需要很长时间才能执行。 如果<超出编译 batchTimeout=/>,批处理编译将在后台线程上继续,并且请求的页面将单独编译。 如果批处理编译成功,程序集将保留到磁盘,并且可以在应用程序重启后重复使用。 但是,如果修改了应用程序的 bin 文件夹中的 global.asax、web.config、machine.config 或程序集,则由于依赖项更改,批处理编译过程将再次执行。

    仔细设计大型网站可能会对性能产生重大影响。 在这种情况下,最好只设置几个根据查询字符串数据改变行为的页面。 一般情况下,需要最大程度地减少请求执行时间,最好使用从网站请求一个或多个页面的 HTTP 客户端平均最后一个字节 (TTLB) 的时间进行监视。

    以下性能计数器最适合用于发现拒绝请求的原因:

    • 进程\% 处理器时间
    • Process\Private Bytes
    • Process\Thread Count
    • Web 服务\ISAPI 扩展请求数/秒
    • ASP.NET\Requests Current
    • ASP.NET\请求已排队
    • ASP.NET\请求等待时间
    • ASP.NET Applications\Pipeline 实例计数
    • ASP.NET 应用程序\应用程序队列中的请求

    阈值:0。 此计数器的值应为 0。 应调查大于此值的值。

  • 请求等待时间。 最新请求在队列中等待的时间(或命名管道)在 inetinfo 和 aspnet_wp (之间存在的毫秒数请参阅请求排队) 的说明。 这不包括在应用程序队列中等待的任何时间。

    阈值:1000。 平均请求应在队列中等待 0 毫秒。

  • 正在运行的工作进程。 当前aspnet_wp工作进程数。 在短时间内,新的工作进程和被替换的工作进程可能会共存。 尽管很少见,但有时会在退出时处理死锁。 如果怀疑这一点,请考虑使用工具来监视工作进程的实例数。 或者,可以使用 Memory\Available Mbytes 性能计数器,因为这些挂起进程将消耗内存。

    阈值:2。 在关闭上一个工作进程期间,可能有两个实例。 如果启用了 webGarden ,则阈值应#CPUs加 1。 大于此值的值可能表示进程在非常短的时间内重启过多。

  • 工作进程重启。 aspnet_wp进程重启的次数。

    阈值:1。 进程重启成本高昂且不可取。 值取决于进程模型配置设置,以及不可预见的访问冲突、内存泄漏和死锁。 如果aspnet_wp重启,应用程序事件日志条目将指示原因。 如果发生访问冲突或死锁,请求将丢失。 如果使用进程模型设置来抢先回收进程,则需要设置适当的阈值。

ASP.NET 应用程序计数器

重启应用程序域或 Web 服务时,此类别中的性能计数器将重置为 0。

  • 缓存总条目数。 缓存中的当前条目数 (User 和 Internal) 。 在内部,ASP.NET 使用缓存来存储创建成本高昂的对象,包括配置对象、保留的程序集条目、 MapPath 方法映射的路径以及进程内会话状态对象。

    注意 “缓存总计”系列性能计数器可用于诊断进程内会话状态的问题。 在缓存中存储过多对象通常是内存泄漏的原因。

  • 缓存总命中率。 所有缓存请求的总命中率与未命中率 (用户和内部) 。

  • 缓存总周转率。 每秒对缓存的添加和删除数 (用户和内部) 。 高周转率表示正在快速添加和删除商品,这可能很昂贵。

  • 缓存 API 条目。 用户缓存中的当前条目数。

  • 缓存 API 命中率。 用户缓存请求的总命中率与未命中率。

  • 缓存 API 周转率。 每秒对用户缓存的添加和删除次数。 高周转率表示正在快速添加和删除商品,这可能很昂贵。

  • 输出缓存条目。 当前在输出缓存中的条目数。

  • 输出缓存命中率。 输出缓存请求的总命中率与未命中率。

  • 输出缓存周转率。 每秒对输出缓存的添加和删除次数。 高周转率表示正在快速添加和删除商品,这可能很昂贵。

  • 管道实例计数。 活动管道实例数。 一个管道实例中只能运行一个执行线程,因此此数字为给定应用程序提供正在处理的最大并发请求数。 管道实例数应稳定。 突然增加表示后端延迟, (请参阅上述) “已拒绝的请求”的说明。

  • 编译总数。 已编译的 ASAX、ASCX、ASHX、ASPX 或 ASMX 文件的数目。 这是编译的文件数,而不是生成的程序集数。 程序集将保留到磁盘并重复使用,直到文件依赖项的创建时间、上次写入时间或长度发生更改。 ASPX 页的依赖项包括 global.asax、web.config、machine.config、bin 文件夹中的依赖程序集以及页面引用的 ASCX 文件。 如果在不修改任何文件依赖项的情况下重启应用程序,则将重新加载保留的程序集,而无需任何编译。 仅当文件最初分析并编译为程序集时,此性能计数器才会递增。

    默认情况下,批处理编译处于启用状态,但是,无论创建了多少个程序集,此计数器都会对每个已分析并编译为程序集的文件递增一次。

    如果编译失败,则计数器不会递增。

  • 预处理过程中的错误。 配置和分析错误的总数。 每次发生配置错误或分析错误时,此计数器都会递增。 即使缓存了配置错误,计数器也会在每次发生错误时递增。

    注意 不要仅依赖“错误”性能计数器来确定服务器是否正常。 卸载 AppDomain 时,它们将重置为零。 但是,它们可用于更深入地了解特定问题。 通常,使用 Application_Error 事件来提醒管理员注意问题。

  • 编译过程中的错误。 编译错误的总数。 响应将缓存,并且此计数器仅递增一次,直到文件更改强制重新编译。 实现自定义错误处理以引发事件。

  • 执行过程中的错误。 运行时错误的总数。

  • 执行期间未处理的错误。 运行时未处理的异常总数。 这不包括以下内容:

    1. 事件处理程序清除的错误 (例如Page_Error或Application_Error) 。
    2. 重定向页面处理的错误。
    3. try/catch 块中发生的错误。
  • 执行期间未处理的错误/秒。运行时每秒未处理的异常总数。

  • 错误总数。 预处理期间的错误、编译期间的错误和执行过程中的错误的总和。

  • 错误总数/秒。预处理期间的错误总数、编译过程中的错误数和每秒执行过程中的错误总数。

  • 正在执行的请求。 当前正在执行的请求数。 当 HttpRuntime 开始处理请求时,此计数器会递增,并在 HttpRuntime 完成请求后递减。 在框架 v1.1 中,此性能计数器中有一个 bug, 2003 年 6 月修补程序汇总包 ASP.NET 1.1 中已修复。 遗憾的是,知识库文章中没有介绍该 bug。 在修复之前,计数器包括将响应写入客户端所花费的时间。

  • 应用程序队列中的请求。 应用程序请求队列中的请求数 (请参阅上述) 排队的请求的说明。 除了“当前请求”之外,应用程序队列中的请求还会提供何时拒绝请求的警告。 如果只有几个虚拟目录,则可能适合将默认 appRequestQueueLimit 增加到 200 或 300,尤其是对于负载较重的缓慢应用程序。

  • 找不到请求。 找不到资源的请求数。

  • 请求未授权。 由于未经授权的访问而失败的请求数。

  • 请求超时。已超时的请求数。

  • 请求成功。 已成功执行的请求数。

  • 请求总数。 应用程序启动以来的请求数。

  • 请求数/秒。每秒执行的请求数。 我更喜欢“Web 服务\ISAPI 扩展请求数/秒”,因为它不受应用程序重启的影响。

进程计数器

使用这些计数器,感兴趣的过程是aspnet_wp和本能。

  • 处理器时间百分比。 此进程的线程使用处理器所花费的时间百分比。

    阈值:70%。 在较长时间内大于此值的值表示需要购买硬件或优化应用程序。

  • 句柄计数

    阈值:10000。 aspnet_wp中的 2000 个句柄计数是可疑的,10,000 远远超出了可接受的限制。 如果所有进程的总句柄计数超过大约 40,000,这在针对 IIS 的拒绝服务攻击期间完全可以实现,则性能将明显下降。

  • 专用字节。 此进程拥有的已提交内存的当前大小(以字节为单位)。 内存泄漏通过一致且长时间的专用字节数增加来标识。 这是用于检测内存泄漏的最佳性能计数器。

    在 IIS 5.0 上运行时,应在 processModel memoryLimit=/> 配置部分中设置专用字节的<内存限制。 在 IIS 6.0 上运行时,应在 IIS 管理器中设置内存限制。 打开应用程序池的属性,并在“回收”选项卡上,指定最大已用内存 (限制(以兆字节为单位) )。 此限制对应于专用字节。 将工作进程的专用字节数与内存限制进行比较,以确定何时回收进程。 System.Web.Caching.Cache 还使用专用字节数和内存限制来确定何时从缓存中清除项,从而避免回收进程。 建议将内存限制为物理 RAM 的 60%,以避免分页,尤其是在新进程因内存消耗过多而替换旧进程时。 请注意, 知识库文章330469 解决了 ASP.NET 无法监视具有大量正在运行进程的服务器上的专用字节数的问题。 此修补程序还使缓存内存管理器能够在存在大量正在运行的进程时正常运行。

    请务必调整具有大量物理 RAM 的计算机的内存限制,以便缓存内存管理器和进程回收正常运行。 例如,假设你的服务器具有 4 GB (GB) 使用默认内存限制的物理 RAM。 这是个问题。 60% 的物理 RAM 为 2.4 GB,大于默认的 2 GB 虚拟地址空间。 那么,内存限制应设置为什么?

    需要考虑以下几点:首先,当“进程\虚拟字节”在虚拟地址空间限制的 600 MB 以内时,遇到 OutOfMemoryException 的可能性开始急剧增加, (通常为 2 GB) ,其次,测试显示“Process\Virtual Bytes”通常大于“Process\Private Bytes”不超过 600 MB。 这种差异在一定程度上是由于 GC 维护的MEM_RESERVE区域,因此可在需要时快速提交更多内存。 综合起来,这意味着当“进程\专用字节”超过 800 MB 时,遇到 OutOfMemoryException 的可能性会增加。 在此示例中,计算机具有 4 GB 的物理 RAM,因此需要将内存限制设置为 20%,以避免内存不足的情况。 可以尝试使用这些数字来最大程度地利用计算机上的内存,但如果想要安全地播放,示例中的数字将起作用。

    总之,请将内存限制设置为小于 60% 的物理 RAM 或 800 MB。 由于 v1.1 支持 3 GB 虚拟地址空间,因此如果将 /3GB 添加到boot.ini,则可以安全地使用 1,800 MB 而不是 800 MB 作为上限。

    请注意,运行测试时,如果要强制使用 GC 并稳定托管内存,可以调用 System.GC.GetTotalMemory (true) 一次。 此方法将调用 GC。重复收集和WaitForPendingFinalizers () ,直到内存稳定在 5% 以内。

    阈值:至少 60% 的物理 RAM 和 800 MB。 大于总物理 RAM 60% 的值开始对性能产生影响,尤其是在应用程序和进程重启期间。 在虚拟地址空间限制为 2 GB 的进程中,当专用字节超过 800 MB 时,OutOfMemoryException 的 likliion 会大大增加。

  • 线程计数。 此进程中处于活动状态的线程数。 当负载过高时,线程计数通常会增加。

    阈值:75 + ( (maxWorkerThread + maxIoThreads) * #CPUs) 。 如果使用 aspcompat 模式,应提高阈值: 阈值:75 + ( (maxWorkerThread + maxIoThreads) * #CPUs * 2)

  • “虚拟字节”。 此过程的虚拟地址空间的当前大小(以字节为单位)。

    除非使用 boot.ini 中的 /3GB 开关启用 3 GB 地址空间,否则用户模式进程的虚拟地址空间限制为 2 GB。 性能会随着此限制的临近而降低,并且通常会导致进程或系统崩溃。 随着 2 GB 或 3 GB 限制的临近,地址空间会变得碎片化,因此我建议分别使用 1.4 或 2.4 GB 的保守阈值。 如果在此处遇到问题,将看到 引发 System.OutOfMemoryException ,这可能会或可能不会导致进程崩溃。

    在 IIS 6.0 上运行时,可以在 IIS 管理器中设置虚拟内存限制。 但是,如果设置不当,可能会导致 ASP.NET 出现问题。 ASP.NET 从缓存中清除项以避免超过专用字节数限制,但算法在此确定中使用专用字节数和专用字节限制。 它不监视虚拟字节数或虚拟字节数限制。 鉴于虚拟字节和专用字节之间的差值通常不超过 600 MB,如果担心虚拟内存泄漏或碎片的可能性,可以将虚拟字节限制设置为大于专用字节限制的值 600 MB。 如果需要,请在应用程序池的“属性”的“回收”选项卡上设置最大虚拟内存 (限制(以 MB) 为单位)。

    框架版本 1.0 不支持工作进程或状态服务中的 3 GB 地址空间。 但是,有关在 inetinfo.exe 内启用 3 GB 地址空间的说明,请参阅知识库文章320353。 版本 1.1 完全支持工作进程和状态服务的 3 GB 地址空间。

    阈值:小于虚拟地址空间大小 600 MB;1.4 或 2.4 GB

处理器计数器

  • 处理器时间百分比。 所有线程使用处理器所花费的时间百分比。

    阈值:70%。 在较长时间内大于此值的值表示需要购买硬件或优化应用程序。

内存计数器

  • 可用 MB。 可用的物理 RAM 量。

    阈值:物理 RAM 的 20%。 应调查小于此值的值,并可能表示需要购买硬件。

系统计数器

  • 上下文切换/秒。处理器切换线程上下文的速率。 高数字可能表示用户与内核模式之间的锁争用或转换过高。 上下文开关/秒应随吞吐量、负载和 CPU 数线性增加。 如果它呈指数级增长,则存在问题。 应使用探查器进行进一步调查。

Web 服务计数器

  • 当前连接。 此计数器的阈值取决于许多变量,例如 (ISAPI、CGI、静态 HTML 等) 、CPU 使用率等的请求类型。 应通过经验制定阈值。
  • 方法请求总数/秒。主要用于诊断性能问题的指标。 将它与“ASP.NET Applications\Requests/sec”和“Web Service\ISAPI 扩展请求数/秒”进行比较可能会很有趣,以便查看静态页面服务的百分比与aspnet_isapi.dll呈现的页面的百分比。
  • ISAPI 扩展请求数/秒。主要用于诊断性能问题的指标。 将其与“ASP.NET Applications\Requests/sec”和“Web Service\Total 方法请求数/秒”进行比较可能很有趣。请注意,这包括对所有 ISAPI 扩展的请求,而不仅仅是aspnet_isapi.dll。

结论

在应用程序上线前对应用程序进行仔细的压力和性能测试可以避免严重的麻烦。 许多人似乎遇到了两个主要的绊脚石:

  1. 需要使用能够模拟网站体验的流量和负载的 HTTP 客户端。
  2. 需要在与生产环境几乎相同的环境中测试整个应用程序。

模拟真实网站流量并不容易,但我可以说,大多数遇到麻烦的应用程序从未经过足够的压力测试。 本文应帮助你了解性能计数器,并创建一些用于监视性能的有用工具。 若要应用负载,我建议 Microsoft Application Center Test (ACT) ,它包含在 Microsoft® Visual Studio® .NET 中。 有关此压力工具的详细信息,请参阅 Microsoft Application Center Test 1.0 Visual Studio .NET Edition。 我还建议使用 Microsoft® Web 应用程序压力工具 (WAST) 。 可以从 TechNet 免费下载。 如果应用程序使用 ViewState,则需要使用 ACT,因为 WAST 无法动态分析响应。

我不知道生产环境是什么,但肯定有一些特别之处。 我数不清我听到这句话的次数,“问题只发生在我们的生产现场。通常,区别在于应用程序本身。 应用程序的某些部分通常无法在实验室中模拟。 例如,测试中省略了广告服务器,或者用于模拟真实数据库的数据库大相径庭。 有时网络或 DNS 差异是原因,有时是运行服务器的硬件的差异。

几年来,我一直在调试和监视 ASP.NET 应用程序的性能,但有时仍需要帮助。 如果你发现自己处于这个位置, ASP.NET 网站上的 论坛是寻求答案的好地方。 但是,如果你真的处于困境中,请随时使用该网站上提供的联系信息联系 Microsoft 产品支持 部门。 请注意,如果 Microsoft 将问题确定为 Microsoft 产品缺陷的结果,则不会针对该事件向你收费。

希望本文档已为你准备好确保应用程序可靠性和性能所需的工具和信息。 如果你有任何问题,请将其发布到 ASP.NET Web 上,我会尽我最大的努力回答这些问题。 祝你好运!

关于作者

Thomas Marquardt 是 Microsoft ASP.NET 团队的开发人员。 自 2000 年冬天以来,他一直在调试和调查 ASP.NET 应用程序的性能问题。 Thomas 感谢 Microsoft ASP.NET 开发经理 Dmitry Robsman 多年来提供数小时帮助和指导。

© Microsoft Corporation. 保留所有权利。