高CPU使用率,可能是由于上下文切换引起的?

9
我们的一台服务器在应用程序中遇到了非常高的 CPU 负载。我们查看了各种统计数据,但存在问题,难以找出问题的根源。
目前的一个理论是线程过多,我们应该尝试减少并发执行线程的数量。只有一个主线程池,具有 3000 个线程,并且有一个与之配合的 WorkManager(这是 Java EE - Glassfish)。在任何给定时刻,大约有 620 个单独的网络 IO 操作需要同时进行(java.NIO 的使用也不是一个选项)。此外,还有大约 100 个没有涉及 IO 并且也在并行执行的操作。
这种结构不高效,我们想知道它是否真正造成了损害,或者只是糟糕的做法。原因是系统中的任何更改都相当昂贵(以人力投入为代价),因此我们需要一些问题的证明。
现在我们在思考线程上下文切换是否是原因,考虑到线程远远超过所需的并发操作。查看日志,我们看到平均每秒钟有 14 个不同的线程执行。如果考虑到存在两个CPU(见下文),那么是每个 CPU 上的 7 个线程。这听起来不像太多,但我们想要验证这一点。
所以 - 我们可以排除上下文切换或线程过多的问题吗?
一般细节:
  1. Java 1.5(是的,它很老),运行在CentOS 5、64位、Linux内核2.6.18-128.el5上
  2. 机器上只有一个Java进程,没有其他东西。
  3. 虚拟机下有两个CPU。
  4. 8GB RAM
  5. 我们不能在机器上运行分析器。
  6. 我们不能升级Java,也不能升级操作系统。
更新 如下建议,我们在测试服务器上使用了负载平均值的捕获(使用uptime)和CPU(使用vmstat 1 120)进行了各种负载的测量。我们在每次负载更改及其测量之间等待15分钟,以确保系统稳定在新负载周围,并且负载平均数已更新:
生产服务器工作量的50%:http://pastebin.com/GE2kGLkk

生产服务器工作量的34%: http://pastebin.com/V2PWq8CG

生产服务器工作量的25%: http://pastebin.com/0pxxK0Fu

CPU使用率似乎随着负载的减少而减少,但不是非常显著(从50%到25%的变化实际上并没有减少50%的CPU使用率)。负载平均值似乎与工作量无关。

还有一个问题:考虑到我们的测试服务器也是虚拟机,它的CPU测量结果是否会受到运行在同一主机上的其他虚拟机的影响(使上述测量结果无用)?

更新2 将线程的快照分为三部分附加(pastebin限制)

第1部分:http://pastebin.com/DvNzkB5z

Part 2: http://pastebin.com/72sC00rc 第二部分:{{链接1:http://pastebin.com/72sC00rc}}
Part 3: http://pastebin.com/YTG9hgF5 第三部分:{{链接2:http://pastebin.com/YTG9hgF5}}

那么,减少线程池中的线程数量会有帮助吗? - Voo
高 CPU 使用率可能是好事:这意味着您对 CPU 资源的利用是最优化的。您的线程正在计算某些内容,而不是等待 I/O 或锁定。除非您有一个不必要地消耗 CPU 的紧密循环,否则您应该对您所实现的高并发水平感到满意。 - Sergey Kalinichenko
1
@dasblinkenlight 如果我们能够证明没有浪费(例如上下文切换)的话,那么这是正确的。如果我们能够做到这一点,我们可以告诉系统团队添加更多的CPU,并证明它的必要性。但首先,我们必须完成我们的功课。 - Yon
@Voo 这是一个可能性,我们会将其添加到列表中。 - Yon
@Voo 在测试环境中尝试减少线程数量并没有起到帮助的作用。 - Yon
6个回答

5
对我来说,问题似乎主要是100个CPU线程而非其他。3000个线程池基本上是一个红鱼,因为空闲线程不会消耗太多资源。 I / O线程大部分时间可能处于睡眠状态,因为I / O的计算机操作时间尺度比较长。

您没有提到这100个CPU线程在做什么或持续多长时间,但如果您想减慢计算机的速度,那么专门使用100个“运行直到时间片停止”的线程肯定会做到。因为您有100个“始终准备好运行”的线程,所以机器将按照调度程序允许的速度进行上下文切换。几乎没有空闲时间。由于CPU线程(可能)占用了大部分CPU时间,因此您的I / O“限制”线程将等待更长时间的运行队列,而不是等待I / O。因此,更多的进程正在等待(I / O进程更容易因为快速到达I / O屏障而使进程无法执行下一个操作而退出)。

毫无疑问,有很多微调可以改善效率,但是100个CPU线程就是100个CPU线程,您在那里不能做太多事情。


谢谢您的见解。看了问题第二次更新中发布的线程堆栈,您认为怎么样? - Yon
经过审查线程堆栈并调整线程池大小等操作,我们得出结论,您在这里是正确的。我们没有减少线程池大小,而是更改了一些代码,使不需要I/O且不等待任何内容的任务将按顺序执行。其他任务是并行执行的,但有一定限制的同时执行的任务数量,这基于我们估计在任何给定时刻将有多少线程处于RUNNABLE状态。 - Yon
上下文切换时间是否也包含在CPU利用率中? - Abhay Patil

4
我认为你的限制条件是不合理的。基本上,你所说的是:
1.I can't change anything
2.I can't measure anything

请推测一下我的问题可能是什么?
实际答案是,您需要将适当的分析器连接到应用程序,并将所见内容与CPU使用率、磁盘/网络I/O和内存相关联。
记住性能调整的80/20法则。 80%将来自于调整应用程序。 您可能只是负载过大,一个VM实例无法承受,此时可能需要考虑通过向机器提供更多资源来进行横向或纵向扩展的解决方案。 可能有三十亿个JVM设置不符合您的应用程序执行特定要求中的任何一个。
我假设3000线程池来自著名的“更多线程=更多并发=更高性能”理论。 真正的答案是,除非您在更改之前/之后测量吞吐量和响应时间并比较结果,否则调整更改没有任何意义。

我们无法完成某些事情的原因是服务器位于地球另一端,背后有几层保护措施。我们需要飞到那里,即使这样,它也没有互联网接入,所以事情变得非常麻烦。我们真的不想这样做。提供更多资源需要说服当地系统团队,这意味着我们需要证据。 - Yon
线程池的出现是因为:并发IO任务的数量可以增长,而我们无法控制。有另一个人(称之为操作者)可能会在对系统了解不多的情况下导致这种增长。因此,我们将3000设置为应该足以处理操作者投入系统的任何工作量的数字。Glassfish中线程池的问题显然是它们无法在运行时调整大小。 - Yon
2
你的理由完全无效。线程池大小不是你给孩子的津贴。为什么不将其设置为40亿呢?它是指示你的应用程序运行环境及其限制的指标。找到正确的数字是一个试错过程。太少,工作会排队,核心会闲置;太多,线程之间切换的成本超过了并发执行的好处。你需要使用科学的力量来找到正确的数字。 - nsfyn55
它需要自动调整,无需人为干预。您建议我们如何做到这一点? - Yon
1
@Yon,没有普适的线程数量,你的线程数量很可能是针对你的环境而定的。 - nsfyn55
显示剩余2条评论

2
所以-我们可以排除上下文切换或过多线程是问题吗?我认为你对抖动的担忧是有道理的。在2 CPU VMware实例中使用3000个线程的线程池(700+并发操作)肯定会导致上下文切换过载和性能问题。限制线程数可能会给您带来性能提升,尽管确定正确的数量将是困难的,并且可能会使用大量的试错。我们需要一些问题的证据。我不确定最好的回答方式,但以下是一些想法:观察VM OS和JVM的负载平均值。如果您看到高负载值(20+),则表明运行队列中有太多东西。是否没有办法在测试环境中模拟负载,以便您可以调整线程池数字?如果您在具有X大小池的模拟负载的测试环境中运行,然后使用X/2运行,您应该能够确定最佳值。您能否将高负载时段与较低负载时段进行比较?您能否绘制这些时间内响应到延迟的数量图表,以查看是否可以看到关于抖动的临界点?如果您可以模拟负载,请确保您不仅仅在“从消防水龙带喝水”的方法下进行测试。您需要模拟负载,可以将其调整为上下调整。从10%开始,缓慢增加模拟负载,同时观察吞吐量和延迟。通过观察吞吐量的平坦或其他偏转,您应该能够看到临界点。

1
@Yon 看起来你在线程和物理核心方面的数量相差甚远,所以我认为在启动时配置1或2个额外的CPU不会有任何区别。你是否尝试在8核或16核的系统上运行该系统? - Gray
1
@Yon 你是在疯人院工作吗?我无法看着这个框,也不能做任何更改。操作员可以随意更改而不进行警告或理由的解释。听起来你的问题是组织上的而非技术上的。把你的应用程序迁移到云端。 - nsfyn55
@Gray - 是的,我无法确定这个盒子是遭受了过多的上下文切换还是仅仅表现出了极度超载的症状。在一个2核心盒子上平均有14个准备好的线程是一个很大的负载,但是,有多少时间花费在切换上是不清楚的,因为我们不知道平均工作项在由池线程运行时完成多少有用的工作,与切换到另一个线程所需的时间相比。 - Martin James
1
@Yon 你是否正在使用“喝水管子”方法来加载你的测试服务器?如果是这样,那么你需要重新设计模拟负载。随着负载的增加,性能会发生什么变化?从10%开始,然后缓慢增加负载,观察延迟和总吞吐量。你应该能够看到当吞吐量趋于平稳或者偏离时的临界点。 - Gray
1
@Yon:“线程的平均时间片是几毫秒” - 如果这是运行时间而不是运行+切换时间,那么你的环境只是超载了。你应该尝试更快/更多的CPU。如果VM只允许2个“处理器”,(我的VMware工作站只允许2个,即使主机有4 + 4HT),那么像另一个帖子建议的那样,尝试在VM外运行,即使只是试用。 - Martin James
显示剩余6条评论

2

1

通常情况下,线程中的上下文切换在计算上非常便宜,但当涉及到这么多线程时...你就无法知道。你说升级到Java 1.6 EE是不可能的,但硬件升级呢?这可能会提供一个快速解决方案,而且不应该太昂贵...


系统团队要求我们提供证据,以解释为什么任何资源变更都是合理的。 - Yon

0

例如,在类似的机器上运行分析器。

  • 尝试使用更新的Java 6或7版本。(如果没有区别,则不要升级生产环境)
  • 尝试Centos 6.x
  • 尝试不使用VMware。
  • 尝试减少线程数。您只有8个核心。

您可能会发现以上所有或其中任何一个选项都有所不同,但在具有已知/可重复工作负载的系统上进行测试之前,您将不会知道。


我们有一个测试环境,运行大约一半的负载。更改Java版本对其没有影响,减少线程数量也没有影响。 - Yon
因此,您可以得出结论,升级Java版本不会有所帮助,线程数量可能也不是问题。 - Peter Lawrey
我们面临的一个问题是:是否存在更多负载服务器唤醒更多线程,从而导致上下文切换引起问题的可能性?重要的是要记住,即使使用更大的线程池,大多数线程仍在等待队列中。 - Yon
如果服务器繁忙,它可能会严重拖慢应用程序的运行速度,特别是当它需要与CPU、内存或IO(例如磁盘或网络)竞争时。如果出现这种情况,在top或任务管理器中应该很明显。 - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接