跨应用域回收使用自定义性能计数器

Posted

技术标签:

【中文标题】跨应用域回收使用自定义性能计数器【英文标题】:Using custom performance counters across appdomain recycles 【发布时间】:2008-12-05 14:41:51 【问题描述】:

我有一个 ASP.NET 应用程序,它通过创建和写入自定义性能计数器来跟踪统计信息。有时,我在错误日志中看到指示计数器无法打开,因为它们已在当前进程中使用。我认为这是由于我的 .NET appdomain 已在同一个 w3wp.exe 进程中重置。当我的应用程序域被回收后,如何避免这些错误并重新建立与我的性能计数器的连接?

计数器构造:

PerformanceCounter pc = new PerformanceCounter();
pc.CategoryName = category_name;
pc.CounterName = counter_name;
pc.ReadOnly = false;
pc.InstanceLifetime =
PerformanceCounterInstanceLifetime.Process;
pc.InstanceName = instance_name;

计数器用法:

pc.Increment()

[2009 年 3 月 26 日更新] 收到的错误信息是:

实例“_lm_w3svc_1_root_myapp”已经存在,进程的生命周期。在它被删除或使用它的进程退出之前,它不能被重新创建或重用。 已经存在一个生命周期的进程。

我尝试通过初始化性能计数器并在瞬态 AppDomain 中写入其中一个来在控制台应用程序中复制异常。然后我卸载 AppDomain 并在第二个 Appdomain 中再次执行此操作(相同的过程)。他们都成功了。我现在不确定这是什么原因,我对 ASP.NET 中 AppDomain 回收的假设似乎是错误的。

【问题讨论】:

你是如何建立连接的?在全球 asax 中? 当使用第一个计数器时,它是懒惰地完成的。这通常是在收到第一个网络请求后不久。 如果我们看到创建计数器的代码以及写入计数器的示例,将会有所帮助。 添加了创建和写入用法。 是否在同一个页面中创建和使用?还是 pc 变量存储在应用程序、会话范围或某个静态变量中? 【参考方案1】:

使用process based WCF performance counters 时,应用程序日志中也可能出现上述错误。其症状是应用程序事件日志中的三个错误块:

未加载性能计数器。 类别名称:ServiceModelService 3.0.0.0 柜台名称:来电 异常:System.InvalidOperationException:实例“abc@def|service.svc”已经存在,进程的生命周期。在它被删除或使用它的进程退出之前,它不能被重新创建或重复使用。

这总是在系统事件日志中的以下信息消息之后立即发生:

服务应用程序池“MyAppPool”的进程 ID 为“nnnn”的工作进程已请求回收,因为该工作进程已达到其允许的处理时间限制。

这会导致此服务的性能监视器计数器不可用,显然是几个小时。

ServiceModelService 3.0.0.0 版本号为depend on the version of .NET you are using(这是使用 .NET 3.5 测试的)。

背景

故障是由工作进程达到其处理时间限制触发的,此时必须回收它。这是在 IIS 应用程序池回收设置中设置的(IIS 6.0 及更高版本,因此是 Windows Server 2003 及更高版本)。工作进程的回收会导致基于新进程的性能计数器名称与旧进程冲突,从而产生错误。这是因为 IIS 使用overlapped recycling,要终止的工作进程会一直运行到新的工作进程启动之后。

复制

(在 Windows Server 2003 = IIS 6.0 上测试)

创建 WCF 服务。 在<system.serviceModel> 部分的web.config 中添加the following:<diagnostics performanceCounters="All" /> 在属性 → 回收下服务的应用程序池属性中,将回收工作进程设置为 1 分钟。手动回收应用程序池无效,因为这不会从工作进程创建回收请求(在带有 W3SVC 信息事件的事件查看器系统日志中很明显)。 在 soapUI 中为 WCF 操作创建测试套件/测试用例/测试步骤/测试请求。 将测试用例添加到soapUI 中的负载测试中,或使用loadUI,以每秒1 次的速率触发调用。 工作进程每 1 分钟请求一次回收(在系统日志中很明显)。每 2 分钟,这将导致来自 System.ServiceModel 3.0.0.0 的应用程序日志中出现一批三个错误。 在工作进程再次回收之前,该服务的性能计数器将变得不可用。 (注意此时将回收周期设置为更高的值,以查看性能计数器不可用的时间将实际回收进程并使计数器再次可用。)

可能的解决方案

解决方案 1 - 红鲱鱼

修补程序 KB981574 取代修补程序 KB971601。后一个修补程序描述了问题:

修复:当应用程序退出并重新启动并且您在运行 .NET Framework 2.0 的计算机上收到 System.InvalidOperationException 异常时,监视应用程序的性能计数器停止响应

应用以前的修补程序并不能解决问题。应用后一个修补程序会导致应用程序池错误。

解决方案 2 - 一个可行的解决方案

可以创建service host factory that exposes a custom service host:

using System;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Activation;

namespace MyNamespace

    public class WebFarmServiceHostFactory : ServiceHostFactory
    
        protected override ServiceHost CreateServiceHost(
                   Type serviceType, Uri[] baseAddresses)
        
            return new WebFarmServiceHost(serviceType, baseAddresses);
        
    

    public class WebFarmServiceHost : ServiceHost
    
        public WebFarmServiceHost(
            Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)  

        protected override void ApplyConfiguration()
        
            base.ApplyConfiguration();

            Description.Name = "W3wp" + Process.GetCurrentProcess().Id +
                               Description.Name;
        
    

并在服务标记.svc文件中引用工厂:

<%@ ServiceHost Language="C#" Debug="true" Factory="MyNamespace.WebFarmServiceHostFactory" Service="WcfService1.Service1" CodeBehind="Service1.svc.cs" %>

最后的调整

不幸的是,尽管这使得问题发生的频率大大降低,但它仍然会发生(大约减少了 30 倍,尽管我没有测量过准确的统计数据)。

似乎可以完全解决问题的一个简单调整是在base.ApplyConfiguration(); 之前添加一个睡眠命令:

Thread.Sleep(1);

这只会在每个工作进程回收时触发一次,因此对服务性能的影响应该可以忽略不计。尽管 1ms 的最小设置对我有用(关于此命令实际休眠多长时间的争论),但您可能必须提高此值(您可以使其可配置)。

解决方案 3 - 最简单的解决方法

在 IIS 中有一个 DisallowOverlappingRotation Metabase Property。这个can be set as follows(MyAppPool 应用程序池的示例):

cscript %SYSTEMDRIVE%\inetpub\adminscripts\adsutil.vbs SET w3svc/AppPools/MyAppPool/DisallowOverlappingRotation TRUE

解决方案比较

当工作进程由于没有 IIS 重叠回收而回收时,解决方案 #3 将 cause your site to be down longer。

在使用 5 个线程每秒处理 100 多个事务的基本 Web 服务的 soapUI 负载测试中,每次工作进程回收时,新事务被阻止的“冻结”是明显的几秒钟。当测试更复杂的 Web 服务时,这种冻结时间会更长(超过 8 秒)。

解决方案 #2 没有产生这样的阻塞,在回收期间有流畅的响应,并且没有产生性能冲突错误。

结论

对于不需要低延迟的 Web 服务,可以使用解决方案 #3。如果您知道一天中的负载分布和安静时间,您甚至可以将回收设置为每天在设定的时间(这可以在 IIS 的同一选项卡中完成)。如果使用网络农场,这甚至可以错开。

对于无法容忍此类延迟的 Web 服务,似乎解决方案 #2 是最好的方法。

【讨论】:

关于解决方案#3 - 也可以从 IIS GUI 完成 - 应用程序池的“高级设置”中名为“禁用重叠回收”的选项。非常感谢详细的解释和分析!【参考方案2】:

IIRC,IIS 不会确保您的第一个 AppDomain 在启动第二个之前已关闭,尤其是当您手动或自动回收它时。我相信,当启动回收时,首先实例化第二个 AppDomain,一旦成功,新的传入请求就会被定向到它,然后 IIS 等待第一个 AppDomain(正在关闭的那个)完成处理它的任何请求有。

结果是存在两个 AppDomain 的重叠部分,两者都具有相同的 instance_name 值。

但是,并非所有问题都已解决。我通过将进程 ID 作为实例名称的一部分在我的代码中更正了这个问题。但它似乎引入了另一个问题——我认为是进程范围的性能计数器似乎永远不会在不重新启动计算机的情况下消失。 (这可能是我的一个错误,所以 YMMV)。

这是我创建实例名称的例程:

    private static string GetFriendlyInstanceName()
    
        string friendlyName = AppDomain.CurrentDomain.FriendlyName;
        int dashPosition = friendlyName.IndexOf('-');
        if (dashPosition > 0)
        
            friendlyName = friendlyName.Substring(0, dashPosition);
        
        friendlyName = friendlyName.TrimStart('_');
        string processID = Process.GetCurrentProcess().Id.ToString();
        string processName = Process.GetCurrentProcess().ProcessName;
        string instanceName = processName + " " + processID + " " + friendlyName.Replace('/', '_').Trim('_').Trim();
        return instanceName;
    

【讨论】:

有趣,我会试一试。你提到的问题在最后介绍了,你是怎么注意到的,有什么影响?是资源泄漏,还是导致其他不良行为? 资源泄漏。我遇到的问题是最终您的实例插槽用完了。当您重新启动计算机时,它们会消失。这可能只是因为我在进程结束时未能调用 RemoveInstance()。我认为在我的情况下,我正在处理一个被杀死而不是完全关闭的进程。 我非常有信心解决了我的问题。我确保在 Dispose 处理程序中为我拥有包装性能计数器的对象调用 RemoveInstance() 方法。【参考方案3】:

我不是自定义计数器方面的专家,但根据您提供的信息,考虑到在添加域即将被回收时某些代码尝试使用计数器的可能性,我认为值得一试。在与 dispose 或 destructor 相关的任何事情中寻找计数器的使用。

【讨论】:

好的,我该怎么办?有什么方法可以检测到应用程序域正在回收并锁定性能计数器的使用?我认为追踪触发计数器写入的每一段代码并确保在回收未决时无法调用它是不现实的。 @Jeremy 尝试获取/查看堆栈跟踪 - 当你有它时也发布它【参考方案4】:

如果您懒惰地创建性能计数器,则可能是线程问题。在进程回收之后,如果同时发生两个页面命中(这并不让我感到惊讶),那么您的性能创建调用可能会运行多次。一方面,您可以放心地忽略此错误。但是如果你想消除它,我建议你用一个锁语句来包装你的性能计数器代码生成,例如

lock (this.lockObject)

 //Create performance counter

【讨论】:

我编写了一些测试代码,从 100 个并发线程中重复创建和使用完全相同的计数器和实例名称,而没有遇到这种情况。到目前为止,我无法证明这是不恰当的。【参考方案5】:

我有一个类似的问题: 不能在 Visual Studio 中多次创建多实例进程生命周期计数器,但这是因为我打开了 PerfMon!

我花了一段时间才意识到这一点。

【讨论】:

【参考方案6】:

IIS 保证您的第一个 AppDomain 在启动第二个之前不会关闭,特别是当您手动或自动回收它时,如果您将应用程序池回收“禁用重叠回收”属性设置为 false(默认)。

如果是共享主机,您可以在 web.config 文件中定义它。

【讨论】:

以上是关于跨应用域回收使用自定义性能计数器的主要内容,如果未能解决你的问题,请参考以下文章

自定义性能计数器触发安全异常。为啥?

请求的性能计数器不是自定义计数器,它必须初始化为 ReadOnly

如何重置自定义性能计数器

在自托管 Web 应用程序中使用性能计数器

自定义 SQL Server 性能计数器

JavaScript性能优化1——内存管理(JS垃圾回收机制引用计数标记清除标记整理V8分代回收Performance使用)