如何诊断 J2EE 系统中的性能问题
时间:2004-12-14 作者:John Bley 浏览次数: 本文关键字:诊断, J2EE, 性能, 测试, 瓶颈 |
|
为什么问题诊断如此复杂?
对于 WebLogic 应用程序的某个具体应用模式来说,没有既定的公式可以用来推导出它的性能(在严格的实时工程当中,速度单调分析这类技术确实可以做这项工作,但是在本文里 还是让我们忘记它吧)。网络上是否存在另外一个系统正在密集使用一个共享的后端服务,对于实际产生的性能有很大影响。性能也许还取决于 JDBC 驱动程序版本和数据库的正确匹配。也许开发人员三年前写的一些代码恰巧在这个时候才出现特定类型的异常,而您急切需要的解决问题回馈,却恰恰包含在这个异 常里。
从本质上说,典型业务系统的性能是由成千上万交互的变量和决策共同作用的结果。就像人体一样,有太多连锁着的部分和过程,所以很难理解系统的整体。因此我们进行了简化,并求助于拱形模式。
疾病
您看到的症状的根本原因是什么?它是初级流行性感冒或者是肺炎的开始吗?是应用程序内部的底层问题还是它所在的 JVM 外部的问题?请参阅表 1 中应用程序性能低下的一些最常见原因。
表1
毛病 | 描述 | 症状 | 原因或治法 |
线性内存泄漏 | 每单位(每事务、每用户等)泄漏造成内存随着时间或负载线性增长。这会随着时间或负载增长降低系统性能。只有重启才有可能恢复。 | 随着时间越来越慢 随着负载越来越慢 |
虽然可能有多种外部原因,但最典型的是与资源泄漏有关(例如,每单位数据的链表存储,或者没有回收的回收/增长缓冲区)。 |
指数方式内存泄漏 | 双倍增长策略的泄漏造成系统内存消耗表现为时间的指数曲线 | 随着时间越来越慢 随着负载越来越慢 |
通常是由于向集合(Vector,HashMap) 中加入永远不删除的元素造成的。 |
糟糕的编码:无限循环 | 线程在 while(true) 语句以及类似的语句里阻塞。 | 可以预见的锁定 | 您需要对循环进行大刀阔斧的删剪。 |
资源泄漏 | JDBC 语句,CICS 事务网关连接,以及类似的东西被泄漏了,造成对 Java 桥接层和后端系统的影响。 | 随着时间越来越慢 可以预见的锁定 突然混乱 |
通常情况下,这是由于遗漏了 finally 块,或者更简单点,就是忘记用 close() 关闭代表外部资源的对象所造成的。 |
外部瓶颈问题 | 后端或者其他外部系统(如鉴权)越来越慢,同样减缓了 J2EE 应用服务器和应用程序 | 持续缓慢 随着负载越来越慢 |
咨询专家(负责的第三方或者系统管理员),获取解决外部瓶颈问题的方法。 |
外部系统 | J2EE 应用程序通过太大或太多的请求滥用后端系统。 | 持续缓慢 随着负载越来越慢 |
清除冗余的工作请求 ,成批处理相似的工作请求,把大的请求分解成若干个更小的请求,调整工作请求或后端系统(例如,公共查询关键字的索引)等。 |
糟糕的编码:CPU密集的组件 | 这是 J2EE 世界中常见的感冒。一些糟糕的代码或大量代码之间一次糟糕的交互,就挂起了 CPU,把吞吐速度减慢到爬行的速度。 | 持续缓慢 随着负载越来越慢 |
典型的解决方案就是数据高速缓存或者性能计数。 |
中间层问题 | 实现得很糟糕的桥接层(JDBC 驱动程序,到传统系统的 CORBA 连接),由于对数据和请求不断的排列、解除排列,从而把所有通过它的流量减慢到爬行速度。这个毛病在早期阶段很容易与外部瓶颈混淆。 | 持续缓慢 随着负载越来越慢 |
检查桥接层和外部系统的版本兼容性。如果有可能,评估不同的桥接供应商。如果重新规划架构,有可能完全不需要桥接。 |
内部资源瓶颈:过度使用或分配不足 | 内部资源(线程、放入池的对象)变得稀缺。是在正确使用的情况下加大负载时出现过度使用还是因为泄漏? | 随着负载越来越慢 零星的挂起或异常错误 |
分配不足:根据预期的最大负载提高池的最大尺寸。过度使用:请参阅外部系统的过度使用。 |
不停止的重试 | 这包括对失败请求连续的(或者在极端情况下无休止的)重试。 | 可以预见的锁定 突然混乱 |
可能就是后端系统完全宕机。在这里,可用性监控会有帮助,或者就是把尝试与成功分开。 |
线程:阻塞点 | 线程在过于积极的同步点上备份,造成交通阻塞。 | 随着负载越来越慢 零星的挂起或异常错误 可以预见的锁定 突然混乱 |
可能同步是不必要的(只要重新设计),或者比较外在的锁定策略(例如,读/写锁)也许会有帮助。 |
线程:死锁/活动锁 | 最普遍,这是您基本的“获得顺序”的问题。 | 突然混乱 | 处理选项包括:主锁,确定的获得顺序,以及银行家算法。 |
测量关键的统计指标
当您负责诊断问题的时候,您应当能够跟踪关于您的 WebLogic 应用程序健康情况的关键统计指标。您能测量什么?有什么工具可以提供帮助呢?
实验工作
有的时候,在一次标杆运行中获得的数据,不足以揭示答案。那么找到答案的机会就在于您还有些有限的预算,可以运行试验或者做实验工作,来完成诊断。您可以运行什么类型的试验呢?您可以修改、观察什么变量呢?
诊断:测试您的推测
在这个时候,您应当已经有了足够的信息,可以形成关于性能瓶颈原因的推测了(请参阅表 1)。为了验证您的推测是否正确或者在多个备选的推测之间进行筛选,您需要分析更多的信息或者在系统上运行更多的标杆测试。这里是一些可以帮助您的指导意见:
示例诊断
让我们来实际研究一个示例。您的 WebLogic 应用程序表现出在负载下越来越慢的症状。您加入的用户越多,系统越慢。一旦消除负载,系统就冷静下来,没有任何后遗症。您对这一主要症状进行测试,发现并得到图 2 所示的以下结果(时间测量针对的是单一典型事务的端到端完成时间)。
表2
负载(用户数) | 来回用时(毫秒) |
10 | 300 |
50 | 471 |
100 | 892 |
150 | 1067 |
应用程序性能在负载下越来越慢
您形成了几个假设。也许这里的毛病是一个糟糕编码的组件,也许是后端系统的瓶颈。它可能是一个同步阻塞点。您怎样才能分清它们的不同呢?假设您还测试了应用程序服务器在负载运行期间的CPU整体使用情况,并得到了表 3 所示的结果。
表3
负载(用户数) | 来回用时(毫秒) | 整体 CPU 时间(%) |
10 | 300 | 30 |
50 | 471 | 33 |
100 | 892 | 37 |
150 | 1067 | 41 |
问题看起来好像是“等待”瓶颈
现在看起来,系统不是 CPU 密集型的,这就是说它的多数时间都花在等待上了。那么是它的内部(例如,同步交通阻塞)或外部(缓慢的数据库)的问题?好,让我们假设我们还稍微多搜集了一些数据,如表 4 所示。
表4
负载(用户数) | 等待数据库连接的线程数量 | JDBC 查询用时(毫秒) |
10 | 2 | 58 |
50 | 3 | 115 |
100 | 3 | 489 |
150 | 4 | 612 |
问题是否出在一个缓慢的 SQL 语句上呢?
现在看起来并不是内部等待数据库连接的瓶颈,相反,好像是 JDBC 查询本身的问题。JDBC 查询不仅随整体事务时间的不同而不同,而且它糟糕的性能还解释了整体性能糟糕的原因。但是,我们还不能完全确定。您仍然还有三个主要的假设要排序:是否数 据库本身慢?应用程序是否对数据库进行了不合理的请求?或者问题是不是出在应用程序和数据库之间的某个层上?您拿出数据库供应商专用的工具,从它的角度查 看响应时间。假设您看到如表 5 所示的数字。
表5
负载(用户数) JDBC | 查询计时(毫秒) | DB SQL 执行的时间(毫秒) |
10 | 58 | 43 |
50 | 115 | 97 |
100 | 489 | 418 |
150 | 612 | 589 |
如果您没有看到这条信息,那么您可能要返回 JDBC 驱动程序,期待能够发现其中的某些同步问题(请记住,CPU 没有被抑制)。幸运的是,在这个案例里,您已经把具体问题的范围缩小到数据库查询上。找出查询请求是否足够合理,需要一些相关领域的知识,需要熟悉系统, 但是在这个案例里,它也许就是发现查询把非索引字段和外键进行了比较。您和 DBA 协作,它修改索引方案,让查询变得更快,而您则找到了您的药方。
结束语
诊断 WebLogic J2EE 应用程序的性能瓶颈是一个艰苦的旅程。请随时保持清醒,把事实与推测分开,总是用实际的证据来确认您的推测。我希望给您带来了思考和实践问题的一些有用的想法的分类。就像调试,这仍然是一项不明确的艺术,但是深思熟虑会带您走出困境。祝您好运!
作者简介 | |
John Bley是Wily Technology 的软件工程师。他有丰富的 Java 编程和架构经验。为了撰写本文,他总结了 Wily 的企业客户的经验,Wily负责管理复杂的 J2EE 环境。 |