提问人:TheSmurf 提问时间:8/25/2008 最后编辑:Bill the LizardTheSmurf 更新时间:9/15/2011 访问量:5892
Windows 服务增加 CPU 消耗
Windows Service Increasing CPU Consumption
问:
在我的工作中,我负责六项 Windows 服务,这些服务是用 C# 2003 编写的。这些服务中的每一个都包含一个计时器,该计时器每分钟左右触发一次,其中大部分工作都发生在计时器上。
我的问题是,当这些服务运行时,它们开始在循环的每次迭代中消耗越来越多的 CPU 时间,即使它们没有有意义的工作要做(即,它们只是在空闲,在数据库中寻找要做的事情)。当它们启动时,每个服务平均使用 4 个 CPU 的 2-3%,这很好。24 小时后,每个服务将在其循环运行期间消耗整个处理器。
谁能帮忙?我不知是什么原因造成的。我们目前的解决方案是每天重新启动一次服务(它们会自行关闭,然后脚本会看到它们处于离线状态,并在凌晨 3 点左右重新启动它们)。但这不是一个长期的解决方案;我担心的是,随着服务变得越来越繁忙,每天重新启动一次可能还不够......但是,由于启动受到重大惩罚(它们都使用NHibernate进行数据访问),因此随着它们变得越来越繁忙,我们不想做的就是更频繁地重新启动它们。
@akmad:没错,这非常困难。
- 是的,随着时间的推移,单独运行的服务将显示相同的症状。
- 不,它没有。我们已经研究过了。这可能发生在上午 10 点或下午 6 点或半夜。没有一致性。
- 我们愿意;他们是。这些服务正在做他们应该做的事情,仅此而已。
- 不幸的是,这需要预先知道服务何时会耗尽 CPU,这是在不可预测的时间表上发生的,而且永远不会很快......这使得事情变得更加困难,因为我的老板会在他们开始出现问题时运行并重新启动它们,而不考虑调试问题。
- 不,他们使用的 RAM 数量相当一致(每个大约 60-80MB,机器上 4GB)。
很好的建议,但请放心,我们已经尝试了所有常见的故障排除方法。我希望这是一个有人可能知道的 .NET 问题,我们可以努力解决。我老板的解决方案(我坚决不想实现)是在数据库中放置一个字段,该字段可以保存多次,以便服务在白天重新启动,这样他就可以让问题消失而不去想它。我拼命寻找真正问题的原因,以便我能够解决它,因为这个解决方案将在大约六个月内变成一场灾难。
@Yaakov Ellis:它们各有不同的功能。一个从异地某个位置的 Oracle 数据库中读取记录;另一个处理这些记录并将属于这些记录的文件传输到我们的系统;第三个检查这些文件,以确保它们是我们期望的样子;另一个是维护服务,它不断检查磁盘空间(我们有足够的空间)等内容,并轮询其他服务器以确保它们处于活动状态;一个运行只是为了确保所有其他系统都在运行并完成他们的工作,监控和报告错误,并重新启动任何未能保持整个系统每天 24 小时运行的东西。
所以,如果你问的是我认为你在问什么,不,所有这些服务(除了通过NHibernate访问数据库)都没有一件共同的事情,我可以指出这是一个潜在的问题。不幸的是,如果这被证明是实际的问题(这不会让我感到非常惊讶),整个事情可能会被搞砸——我最终会用简单的 SQL 重写所有这些问题。我希望这是一个垃圾收集器问题,或者比 NHibernate 更容易处理的问题。
@Joshdan:没什么秘密。正如我所说,我们已经尝试了所有常见的故障排除方法。分析是无用的:我们使用的探查器无法指向任何在 CPU 使用率较高时实际执行的代码。大约一个月前,这些服务被拆散了,寻找这个问题。对代码的每一部分都进行了分析,以试图找出我们的代码是否是问题所在;我不是来问的,因为我没有做功课。如果这是一个简单的案例,即服务所做的工作比预期的要多,那么就会被抓住。
这里的问题是,大多数时候,服务根本没有做任何事情,但仍然设法消耗了四个 CPU 内核的 25% 或更多:它们发现没有工作可做,并退出循环并等待下一次迭代。从字面上看,这应该几乎不需要 CPU 时间。
下面是我们看到的行为示例,在两天没有工作可做的服务上(在不变的环境中)。这是上周拍摄的:
第 1 天上午 8 点:平均 CPU 使用率约为 3% 第 1 天下午 6 点:平均 CPU 使用率约为 8% 第 2 天上午 7 点:平均 CPU 使用率约 20% 第 2 天上午 11 点:平均 CPU 使用率约 30%
在研究了所有可能的世俗原因之后,我在这里问了这个问题,因为我认为(事实证明是正确的)我会得到更多创新的答案(比如 Ubiguchi 的答案),或者指向我没有想到的事情(比如 Ian 的建议)。
CPU 峰值也会发生 紧接计时器之前 callback,在 timer 回调中, 或紧跟计时器 回调?
你误会了。这不是峰值。如果是这样,那就没有问题了;我可以处理尖峰。但事实并非如此......CPU 使用率普遍上升。即使服务什么都不做,等待下一个计时器命中。当服务启动时,一切都很好,很平静,图表看起来就像你所期望的......通常,使用率为 0%,当 NHibernate 访问数据库或服务执行一些微不足道的工作时,使用率会飙升至 10%。但是,在进程运行时,这始终增加到 25%(如果我让它走得太远,则更高)的使用率。
这使得 Ian 的建议成为合乎逻辑的灵丹妙药(当你不看的时候,NHibernate 会做很多事情)。唉,我已经实施了他的解决方案,但它没有效果(我没有证据证明这一点,但我实际上认为它让事情变得更糟......现在的平均使用量似乎上升得更快)。请注意,剥离 NHibernate“部分”(如您所建议)是不可行的,因为这会剥离服务中大约 90% 的代码,这将使我排除计时器作为问题(我绝对打算尝试),但不能帮助我排除 NHibernate 作为问题,因为如果 NHibernate 导致这种情况, 那么实施的狡猾的修复程序(见下文)将不得不成为系统工作方式;我们在这个项目上非常依赖NHibernate,以至于PM根本不会接受它导致了无法解决的结构性问题。
我刚刚注意到一种绝望感 问题 -- 你的问题 除非出现小奇迹,否则会继续
不要故意让它以这种方式脱落。目前,这些服务每天都在重新启动(可以选择输入一天中的任意小时数来关闭和重新启动),这修补了问题,但一旦它们进入生产机器并开始变得繁忙,就不能成为长期解决方案。无论我修复它们还是 PM 对它们保持这种限制,问题都不会继续存在。显然,我更愿意实施真正的修复,但由于最初的测试没有显示任何原因,并且服务已经过广泛审查,因此 PM 宁愿让它们多次重新启动,也不愿花更多时间尝试修复它们。这完全超出了我的控制范围,使你所说的奇迹比其他情况更重要。
这是非常有趣的(到目前为止 因为您信任您的探查器)。
我没有。但是,这些是用 .NET 1.1 编写的 Windows 服务,运行在 Windows 2000 计算机上,由狡猾的 Nant 脚本部署,使用旧版本的 NHibernate 进行数据库访问。在那台机器上,我几乎不会说我信任。
答:
显然,远程调试您的未知应用程序非常困难......但这里有一些我想看的东西:
- 如果一次只运行其中一个服务,会发生什么情况?你还看到速度变慢了吗?这可能表示服务之间存在一些争用。
- 无论服务运行了多长时间,问题是否总是在同一时间发生?这可能表明其他原因(备份、病毒扫描等)导致整个计算机(或数据库)速度变慢。
- 您是否有日志记录或其他机制来确保服务仅按照您认为应有的频率执行工作?
- 如果可以看到性能在短时间内下降,请尝试运行服务一段时间,然后附加探查器以准确查看 CPU 的固定因素。
- 您没有提到任何有关内存使用情况的信息。您是否有这些服务的任何信息?您可能用完了大部分 RAM 并导致磁盘成为垃圾或类似问题。
祝你好运!
“害怕这个答案只会建议一些方向供您查看,但是在.NET Windows服务中看到类似的问题后,我有一些想法可能会对您有所帮助。
我的第一个建议是,您的服务在处理内存的方式上可能存在一些错误,或者在处理非托管内存的方式上可能存在一些错误。上次我追踪到类似的问题时,原来是一个第三方 OSS 库,我们使用静态内存中非托管对象的存储句柄。服务运行的时间越长,服务拾取的句柄就越多,这导致进程的 CPU 性能迅速下降。尝试解决此类问题的方法,以确保您的服务在计时器调用之间不会在内存中存储任何内容,但如果第三方库使用静态内存,您可能需要做一些聪明的事情,例如为计时器调用创建一个应用程序域,并在处理完成后放弃应用程序 doamin(及其静态内存)。
我在类似情况下看到的另一个问题是计时器同步代码是可疑的,这实际上允许多个线程同时运行处理代码。当我们调试代码时,我们发现第一个线程阻塞了第二个线程,当第二个线程启动时,第三个线程被阻塞了。随着时间的流逝,阻塞的持续时间越来越长,因此 CPU 使用率正在达到顶峰。我们用来解决这个问题的解决方案是实现正确的同步代码,这样计时器就不会在不被阻塞的情况下启动另一个线程。
希望这会有所帮助,但如果我的两个想法都是红鲱鱼,请提前道歉。
评论
听起来像是计时器的线程问题。你可能有一个工作单元阻止另一个工作单元在不同的工作线程上运行,导致它们在每次计时器触发时堆积起来。或者,您的生活和工作时间可能比您预期的要长。
我建议重构计时器。将其替换为对 ThreadPool 上的工作进行排队的单个线程。你可以 Sleep() 线程来控制它查找新工作的频率。确保这是代码是多线程的唯一位置。所有其他对象都应在工作准备好进行处理时实例化,并在该工作完成后销毁。STATE IS THE ENEMY 在多线程代码中。
该设计缺乏的另一个方面似乎是,您有多个服务正在轮询资源以执行某些操作。我建议将它们统一到一个服务下。他们可能会做不同的事情,但他们是齐心协力的;你只是使用文件系统、数据库等来代替方法调用。还有,2003年?我为你感到难过。
评论
我建议把问题切成碎片。
首先,找到一种方法来 100% 快速地重现问题。降低计时器,以便服务更频繁地启动(例如,比正常情况快 10 倍)。如果问题出现的速度快 10 倍,那么它与迭代次数有关,而不是与实时或服务完成的实际工作有关。而且,您将能够比每天更快地完成后续步骤。
其次,注释掉所有实际工作代码,只让服务、计时器和同步机制。如果问题仍然出现,那么它将出现在代码的那部分。
如果没有,那么开始添加你注释掉的代码,一次一个。最终,您应该找出导致问题的代码部分。
您提到您正在使用 NHibernate - 您是否在适当的时间点(例如每次迭代结束时)关闭 NHibernate 会话?
否则,加载到内存中的对象映射的大小将随着时间的推移逐渐增加,并且每次会话刷新将花费越来越多的 CPU 时间。
评论
很好的建议,但请放心,我们已经尝试了所有常见的故障排除方法。我希望这是一个有人可能知道的 .NET 问题,我们可以努力解决。
我的感觉是,无论根本原因多么奇怪,通常的故障排除步骤都是找到问题的最佳选择。
由于这是一个性能问题,因此良好的测量非常宝贵。整个进程 CPU 使用率的衡量范围太广了。您的服务将时间花在哪里?您可以使用探查器来测量这一点,或者只记录各个部分的开始和停止。如果你连这点都做不到,那就用Andrea Bertani的建议——通过删除其他部分来隔离部分。
一旦你找到了一般区域,你就可以进行更细粒度的测量,直到你整理出CPU使用率的来源。如果在这一点上如何解决它并不明显,那么你至少有一个更具体的问题的弹药。
如果您实际上已经完成了所有这些常规故障排除,请让我们了解这个秘密。
这是我要开始的地方:
- 获取进程资源管理器,并在 JIT 中显示 %Time,在 GC 中显示 %Time,在 CPU Cycles Delta、CPU Time、CPU % 和 Threads 中显示。
- 您还需要内核和用户时间,以及一些具有代表性的堆栈跟踪,但我认为您必须点击属性才能获取快照。
- 比较拍摄前后的情况。
关于可能性的几点想法:
- 过量 GC (% GC 中的时间上升。此外,Perfmon GC 和 CPU 计数器将对应)
- 过多的线程和关联的上下文切换(# 个线程上升)
- 轮询(堆栈跟踪始终在单个函数中捕获)
- 内核时间过长(内核时间过长 - 当 CPU 时间过高时,任务管理器显示较大的内核时间数字)
- 异常(PE .NET 选项卡 引发的异常很高,并且越来越高。还有一个 Perfmon 计数器)
- virus/rootkit(好吧,这是最后的场景 - 但可以构造一个隐藏在 TaskManager 中的 rootkit。我怀疑,如果你足够狡猾,你可以将不可避免的CPU使用率分配给另一个进程。此外,如果你排除了以上所有,我现在就没有想法了)
评论