控制影响Java Web API应用程序性能的线程问题

原创|使用教程|编辑:郑恭琳|2020-07-13 14:39:57.827|阅读 11 次

概述:在本文中,学习如何监控Java线程以了解应用程序中引起性能问题的特定代码行。

# 您正在找协同办公软件吗?点击这里站长给您推荐 #

相关链接:

在本文中,学习如何监控Java线程以了解应用程序中引起性能问题的特定代码行。

与线程相关的问题可能会以某种方式对Web API应用程序的性能产生不利影响,这些方式通常难以诊断且难以解决。清晰了解线程的行为对于实现最佳性能至关重要。在本文中,我将向您展示如何使用Parasoft SOAtest的负载测试JVM线程监视器来查看JVM的线程活动,以及动态统计图和可配置的线程转储图,这些图可以指向造成性能损失的代码行。线程使用效率低下。使用Parasoft SOAtest的负载测试模块,您可以将任何功能测试转换为负载和性能测试。

我们将跟随一个假想的Java开发团队,在创建Web API应用程序时遇到一些常见的线程问题,并诊断一些与线程相关的常见性能问题。之后,我们将看一下实际应用程序的更复杂的示例。(请注意,以下示例中的一些次优代码是出于演示目的而有意添加的。)


银行申请


我们假设的Java开发团队着手进行一个新项目——REST API银行应用程序。该团队建立了一个持续集成(CI)基础结构来支持新项目,其中包括使用Parasoft SOAtest的负载测试模块进行的定期CI工作,以连续测试新应用程序的性能。(有关如何设置自动化性能测试的更多详细信息,请参阅我以前的文章《DevOps交付管道中的负载和性能测试》。)

银行应用程序版本1:初始实施中的竞争条件

Bank应用程序代码开始增长,并且测试正在运行。但是,团队注意到,在实施新的转帐操作之后,银行应用程序开始在较高的负载下出现零星的故障。该失败来自帐户验证方法,该方法有时会在透支保护帐户中发现负余额。帐户验证失败会导致异常和API的HTTP 500响应。开发人员怀疑这可能是由处理同一帐户上的并发转移操作的不同线程调用的IAccount.withdraw方法中的竞争条件引起的:

13: public boolean transfer(IAccount from, IAccount to, int amount) {
14:    if (from.withdraw(amount)) {
15:        to.deposit(amount);
16:        return true;
17:    }
18:    return false;
19: }

银行应用程序版本2:添加同步

开发人员决定在转帐操作中同步对帐户的访问,以防止出现可疑的比赛情况:

14: public boolean transfer(IAccount from, IAccount to, int amount) {
15:     synchronized (to) {
16:         synchronized (from) {
17:             if (from.withdraw(amount)) {
18:                 to.deposit(amount);
19:                 return true;
20:             }
21:         }
22:     }
23:     return false;
24: }

该团队还将JVM线程监视器添加到针对REST API应用程序运行的负载测试项目。该监视器将提供死锁、阻塞、驻留和总线程的图表,并将记录这些状态下的线程转储。

代码更改被推送到存储库,并由CI性能测试过程获取。第二天,开发人员发现性能测试在一夜之间失败了。开始进行转帐操作性能测试后不久,Bank应用程序停止响应。快速查看“负载测试”报告中的“JVM线程监视器”图,可以发现Bank应用程序中存在死锁线程(请参见图1.a)。死锁详细信息由JVM线程监视器保存为报告的一部分,并显示了导致死锁的确切代码行(请参见清单1.b)。

图1.a-被测应用程序(AUT)中死锁的线程数。

DEADLOCKED thread: http-nio-8080-exec-20
    com.parasoft.demo.bank.v2.ATM_2.transfer:15
    com.parasoft.demo.bank.ATM.transfer:21
    ...
    Blocked by:
        DEADLOCKED thread: http-nio-8080-exec-7
            com.parasoft.demo.bank.v2.ATM_2.transfer:16
            com.parasoft.demo.bank.ATM.transfer:21
            com.parasoft.demo.bank.v2.RestController_2.transfer:29
            sun.reflect.GeneratedMethodAccessor58.invoke:-1
            sun.reflect.DelegatingMethodAccessorImpl.invoke:-1
            java.lang.reflect.Method.invoke:-1
        org.springframework.web.method.support.InvocableHandlerMethod.doInvoke:209

清单1.b——JVM线程监视器保存的死锁详细信息

银行应用程序版本3:解决僵局

银行应用程序开发人员决定通过在单个全局对象上进行同步来解决死锁,并修改传输方法代码,如下所示:

14: public boolean transfer(IAccount from, IAccount to, int amount) {
15:     synchronized (Account.class) {
17:         if (from.withdraw(amount)) {
18:             to.deposit(amount);
19:             return true;
20:         }
21:     }
22:     return false;
23: }

该更改解决了版本2的死锁问题和版本1的竞争状况,但是平均传输操作响应时间从30毫秒增加到150毫秒以上,增加了五倍以上(见图2.a)。JVM线程监视器的BlockedRatio图形显示,在负载测试执行期间,有60%到75%的应用程序线程处于BLOCKED状态(请参见图2.b)。监视器保存的详细信息表明,尝试进入第15行的全局同步部分时,应用程序线程被阻止(请参见清单2.c)。

解决银行申请僵局

   BLOCKED thread: http-nio-8080-exec-4
    com.parasoft.demo.bank.v3.ATM_3.transfer:15
    com.parasoft.demo.bank.ATM.transfer:21
    com.parasoft.demo.bank.v3.RestController_3.transfer:29
    ...
    Blocked by:
        SLEEPING thread: http-nio-8080-exec-8
            java.lang.Thread.sleep:-2
            com.parasoft.demo.bank.Account.doWithdraw:64
            com.parasoft.demo.bank.Account.withdraw:31

清单2.c——JVM线程监视器保存的阻塞线程详细信息

银行应用程序版本4:提高同步性能

开发团队寻找一种解决方案,该解决方案可以解决竞态条件而又不会引入死锁和损害应用程序的响应能力,并且经过一些研究找到了使用java.util.concurrent.locks.ReentrantLock类的有前途的解决方案:

19: private boolean doTransfer(Account from, Account to, int amount) {            
20:    try {
21:        acquireLocks(from.getReentrantLock(), to.getReentrantLock());
22:        if (from.withdraw(amount)) {
23:            to.deposit(amount);
24:            return true;
25:        }
26:        return false;
27:     } finally {
28:         releaseLocks(from.getReentrantLock(), to.getReentrantLock());
29:     }
30: 

3a中的图形在红色图形中显示了版本4(优化锁定)的银行应用程序转移操作的响应时间,在蓝色图形中显示了版本3(全局对象同步)的响应时间,在绿色图形中显示了版本1(非同步转移操作)的响应时间。这些图表明,由于锁定优化,转移操作性能得到了显着改善。同步(红色图)和非同步(绿色图)传输操作之间的细微差别是防止竞争条件的可接受价格。

3.a——银行应用程序版本4(红色)、版本3(蓝色)和版本1(绿色)的传输操作响应时间。


现实世界中的例子


示例1:增加应用程序响应时间

上面的“银行应用程序”示例旨在说明如何解决由线程问题导致的性能下降的典型隔离情况。实际情况可能更复杂——4中的图形显示了一个生产REST API应用程序的示例,该应用程序的响应时间随着性能测试的进行而不断增长。在测试的上半部分,应用程序响应时间以较低的速率增长,在下半部分中以较高的速率增长(见图4.a)。在测试的前半部分,响应时间的增长与应用程序线程在“阻塞”状态下花费的总时间相关(请参见图4.b)。在测试的后半部分,响应时间的增长与处于PARKED状态的应用程序线程数相关。负载测试JVM线程监视器捕获的堆栈跟踪提供了详细信息:一个指向同步块,该块导致在BLOCKED状态下花费过多时间。另一个指出使用java.util.concurrent.locks类进行同步的代码行,该类负责使线程保持在PARKED状态。在优化了这些代码区域之后,两个性能问题都得到解决。

示例2:应用程序响应时间中的偶发事件

负载测试JVM线程监视器可以非常有用地捕获与线程相关的罕见问题的详细信息,尤其是在性能测试是自动执行且定期运行的情况下。图5中的图形显示了生产REST API应用程序,该应用程序在平均和最大响应时间上都有间歇性的峰值(见图5.a)。

应用程序响应时间的这种峰值通常可能是由于JVM垃圾收集器配置欠佳所致,但是在这种情况下,BlockedTime监视器中的相关峰值(请参见图5.b)指出线程同步是问题的根源。BlockedThreads监视器通过捕获阻塞线程和阻塞线程的堆栈跟踪,在这里提供了更多帮助。重要的是要了解BlockedTime和BlockedThreads监视器之间的区别。

BlockedTime监视程序显示JVM线程在监视程序调用之间处于BLOCKED状态所花费的累积时间,而BlockedThreads监视程序则对JVM线程进行定期快照,并在这些快照中搜索被阻止的线程。因此,BlockedTime监视器在检测线程阻塞方面更为可靠,但它只是警告您存在线程阻塞问题。BlockedThreads监视器因为它获取常规线程快照而可能会丢失某些线程阻塞事件,但从正面来看,当它捕获此类事件时,它会提供导致阻塞的详细信息。因此,BlockedThreads监视器是否将捕获与代码相关的阻塞状态的详细信息是一个统计问题,但是如果定期进行性能测试,您很快就会在BlockedThreads图中看到峰值(参见图5.c),这意味着已捕获阻塞和阻塞线程的详细信息。这些详细信息将使您指向导致应用程序响应时间极少出现尖峰的代码行。

创建性能回归控件

负载测试JVM线程监视器除了是一种有效的诊断工具外,还可以用于创建与线程相关的问题的性能回归控件。发现并解决了此类性能问题后,请为其创建性能回归测试。该测试将包括现有或新的性能测试运行以及新的回归控件。对于Parasoft负载测试,这将是相关JVM线程监控器通道的QoS监控器指标。例如,对于示例1(图4)中描述的问题,创建一个负载测试QoS监视器度量标准,该度量标准检查应用程序线程处于“阻塞”状态所花费的时间,以及另一个度量标准,用于检查处于“已驻留”状态的线程数。在Java应用程序中创建命名线程始终是一个好主意,这将使您可以将性能回归控件应用于经过名称过滤的一组线程。


在自动性能测试中使用Java线程监视器


下表总结了哪些线程监视器通道以及何时使用:

线程监控器通道
何时使用

死锁线程

MonitorDeadlockedThreads


一般来说,死锁可以说是与线程相关的最严重的问题,它可能会完全破坏应用程序的功能。

阻塞线程

封锁

封锁比率

BlockedCount

常规情况中,“阻塞”状态下花费的时间过多或“阻塞”线程数通常会导致性能下降。监视这些参数中的至少一个。也可用于性能回归控制。
停放线程
一般来说,处于PARKED状态的线程数量过多可能表明java.util.concurrent.locks类的使用不正确以及其他线程问题。也可用于性能回归控制。
总线程
常规情况中,用于将处于阻塞,驻留或其他状态的线程数与线程总数进行比较。 也可用于性能回归控制。

睡眠线程

等待线程

等待时间

等待比率

等待计数

偶尔,用于与这些状态相关的性能回归控制和探索性测试。

新线程

未知线程

很少,用于与这些线程状态相关的性能回归控件。


结论


Parasoft的JVM Threads Monitor是一种有效的诊断工具,可检测与线程相关的JVM性能问题并创建高级性能回归控件。与SOAtest的负载测试连续体结合使用时,JVM线程监视器通过记录相关的线程详细信息(这些代码行导致性能不佳)来帮助消除重现性能问题的步骤,并帮助您提高应用程序性能以及开发人员和质量保证生产率。



标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,尊重他人劳动成果

登录 慧都网发表评论


暂无评论...

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
相关厂商
相关产品
Parasoft SOAtest

业内最全面的API、云服务和SOA测试平台,提供优秀的负载与性能测试、API安全测试等功能

Parasoft SOAtest with Load Test

通过使用现有的功能测试来解锁早期的负载和性能测试

在线
客服
咨询
电话
400-700-1020
在线
QQ
购物车 反馈 返回
顶部
在线客服系统
live chat