6

我有一个 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 回收的假设似乎是错误的。

4

6 回答 6

5

使用基于进程的 WCF 性能计数器时,应用程序日志中也可能会遇到上述错误。其症状是应用程序事件日志中的三个错误块:

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

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

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

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

ServiceModelService 3.0.0.0版本号取决于您使用的 .NET 版本(这是使用 .NET 3.5 测试的)。

背景

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

再生产

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

  • 创建 WCF 服务。
  • 将以下内容添加到部分web.config<system.serviceModel>
    <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 - 一个可行的解决方案

可以创建一个公开自定义服务主机的服务主机工厂

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 倍,尽管我没有测量过准确的统计数据)。

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

Thread.Sleep(1);

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

解决方案 3 - 最简单的修复

在 IIS 中有一个DisallowOverlappingRotationMetabase Property。这可以设置如下MyAppPool应用程序池的示例):

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

解决方案比较

解决方案#3 将导致您的站点在工作进程由于没有 IIS 重叠回收而进行回收时停机更长时间。

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

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

结论

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

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

于 2012-10-22T09:06:27.477 回答
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;
    }
于 2009-06-17T18:23:25.690 回答
0

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

于 2009-03-26T23:32:28.083 回答
0

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

lock (this.lockObject)
{
 //Create performance counter
}
于 2009-03-30T18:37:16.077 回答
0

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

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

于 2009-11-05T15:21:17.007 回答
0

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

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

于 2013-11-08T10:48:21.380 回答