Holen Sie sich die neuesten wichtigen Update-Informationen für die Log4j-Sicherheitslücke. Sehen Sie sich an, wie Sie das Problem mithilfe der Parasoft-Anleitung beheben können. Erfahren Sie mehr >>

X
BLOG

Übernehmen Sie die Kontrolle über Threading-Probleme, die sich auf die Leistung Ihrer Java Web API-Anwendung auswirken

Übernehmen Sie die Kontrolle über Threading-Probleme, die sich auf die Leistung Ihrer Java Web API-Anwendung auswirken Lesezeit: 8 Minuten
In diesem Beitrag erfahren Sie, wie Sie Java-Threads überwachen, um die spezifischen Codezeilen in Ihrer Anwendung zu verstehen, die Leistungsprobleme verursachen.

Thread-bezogene Probleme können die Leistung einer Web-API-Anwendung auf eine Weise beeinträchtigen, die häufig schwer zu diagnostizieren und schwierig zu lösen ist. Ein klares Bild des Verhaltens eines Threads ist wichtig, um eine optimale Leistung zu erzielen. In diesem Beitrag zeige ich Ihnen, wie man es benutzt Parasoft SOAtestLoad Test JVM Threads Monitor, um die Threading-Aktivität einer JVM mit Diagrammen wichtiger Statistiken und konfigurierbaren Thread-Dumps anzuzeigen, die auf die Codezeilen verweisen können, die für Leistungsverluste aufgrund ineffizienter Thread-Verwendung verantwortlich sind. Parasoft SOAtests Lastprüfung Mit diesem Modul können Sie alle Funktionstests in Last- und Leistungstests umwandeln.

Wir folgen einem hypothetischen Java-Entwicklungsteam, das beim Erstellen einer Web-API-Anwendung auf einige häufig auftretende Threading-Probleme stößt, und diagnostizieren einige häufig auftretende Thread-bezogene Leistungsprobleme. Danach sehen wir uns komplexere Beispiele für die realen Anwendungen an. (Beachten Sie, dass ein suboptimaler Code in den folgenden Beispielen zu Demonstrationszwecken absichtlich hinzugefügt wurde.)

Die Bankanwendung

Unser hypothetisches Java-Entwicklungsteam hat ein neues Projekt gestartet - eine REST-API-Banking-Anwendung. Das Team richtete zur Unterstützung des neuen Projekts eine CI-Infrastruktur (Continuous Integration) ein, die einen regelmäßigen CI-Job bei Parasoft umfasst SOAtest's Lastprüfung Modul zum kontinuierlichen Testen der Leistung der neuen Anwendung. (Weitere Informationen zum Einrichten automatisierter Leistungstests finden Sie in meinem vorherigen Beitrag. Last- und Leistungstests in einer DevOps Delivery-Pipeline.)

Bankanwendung Version 1: Rennbedingungen in der Erstimplementierung

Der Bank-Anwendungscode wächst und die Tests werden ausgeführt. Das Team bemerkte dies jedoch nach der Implementierung eines neuen transfer Betrieb, begann die Bank-Anwendung sporadische Fehler unter höherer Last. Der Fehler ist auf die Kontovalidierungsmethode zurückzuführen, bei der gelegentlich ein negativer Saldo in überziehungsgeschützten Konten festgestellt wird. Der Fehler bei der Kontoüberprüfung führt zu einer Ausnahme und einer HTTP 500-Antwort von der API. Die Entwickler vermuten, dass dies durch eine Rennbedingung in der verursacht werden kann IAccount.withdraw Methode, wenn sie von verschiedenen Threads gleichzeitig aufgerufen wird transfer Betrieb auf dem gleichen Konto:

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: }

Bankanwendung Version 2: Hinzufügen der Synchronisation

Die Entwickler beschließen, den Zugriff auf Konten innerhalb des zu synchronisieren transfer Betrieb zur Verhinderung des vermuteten Rennzustands:

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: }

Das Team fügt außerdem den JVM-Thread-Monitor zum Lasttestprojekt hinzu, das für die REST-API-Anwendung ausgeführt wird. Der Monitor liefert Diagramme von blockierten, blockierten, geparkten und Gesamt-Threads und zeichnet Dumps von Threads in diesen Zuständen auf.

Die Codeänderung wird in das Repository übertragen und vom CI-Leistungstestprozess erfasst. Am nächsten Tag stellen die Entwickler fest, dass der Leistungstest über Nacht fehlgeschlagen ist. Die Bankanwendung reagierte kurz nach Beginn des Leistungstests für den Übertragungsvorgang nicht mehr. Das Überprüfen der Diagramme des JVM-Threads-Monitors im Lasttestbericht zeigt schnell, dass in der Bank-Anwendung Deadlock-Threads vorhanden sind (siehe Abb. 1.a). Die Deadlock-Details wurden vom JVM-Thread-Monitor als Teil des Berichts gespeichert und zeigen die genauen Codezeilen an, die für den Deadlock verantwortlich sind (siehe Listing 1.b).

Abb. 1.a - Anzahl der festgefahrenen Gewinde in der zu testenden Anwendung (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

Listing 1.b - Vom JVM-Threads-Monitor gespeicherte Deadlock-Details

Bankanwendung Version 3: Deadlocks beheben

Die Entwickler von Bankanwendungen beschließen, den Deadlock durch Synchronisieren auf einem einzelnen globalen Objekt zu beheben und den Code der Übertragungsmethode wie folgt zu ändern:

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: }

Die Änderung behebt das Deadlock-Problem von Version 2 und die Race-Bedingung von Version 1, aber den Durchschnitt transfer Die Reaktionszeit des Betriebs erhöht sich mehr als um das Fünffache von 30 auf über 150 Millisekunden (siehe Abb. 2.a). Das BlockedRatio-Diagramm des JVM-Thread-Monitors zeigt, dass sich 60 bis 75 Prozent der Anwendungsthreads während der Ausführung des Auslastungstests im Status BLOCKIERT befinden (siehe Abb. 2.b). Die vom Monitor gespeicherten Details zeigen an, dass Anwendungsthreads blockiert sind, während versucht wird, den global synchronisierten Abschnitt in Zeile 15 aufzurufen (siehe Listing 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

Listing 2.c - Details zum blockierten Thread, die vom JVM-Thread-Monitor gespeichert wurden

Bankanwendung Version 4: Verbesserung der Synchronisationsleistung

Das Entwicklerteam sucht nach einem Fix, der den Race-Zustand löst, ohne Deadlocks einzuführen und die Reaktionsfähigkeit der Anwendung zu beeinträchtigen, und findet nach einigen Recherchen eine vielversprechende Lösung unter Verwendung von java.util.concurrent.locks.ReentrantLock Klasse:

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: 

Die Grafiken in Abb. 3a zeigen die Antwortzeiten der Bankanwendung transfer Betrieb von Version 4 (optimierte Sperrung) im roten Diagramm, Version 3 (globale Objektsynchronisation) im blauen Diagramm und Version 1 (nicht synchronisierter Übertragungsvorgang) im grünen Diagramm. Die Grafiken zeigen, dass die transfer Die Betriebsleistung hat sich durch die Verriegelungsoptimierung dramatisch verbessert. Der geringfügige Unterschied zwischen dem synchronisierten (rotes Diagramm) und dem nicht synchronisierten (grünes Diagramm) transfer Der Betrieb ist ein akzeptabler Preis, um die Rennbedingungen zu verhindern.

 

Abb. 3.a - transfer Betriebsreaktionszeit der Bankanwendung Version 4 (rot), Version 3 (blau) und Version 1 (grün).

Real World Beispiele

Beispiel 1: Wachsende Reaktionszeit der Anwendung

Die obigen Beispiele für Bankanwendungen dienen dazu, zu demonstrieren, wie typische Einzelfälle von Leistungseinbußen aufgrund von Threading-Problemen behoben werden können. Die Fälle in der realen Welt können komplizierter sein - die Grafiken in Abb. 4 zeigen ein Beispiel für eine Produktions-REST-API-Anwendung, deren Antwortzeit im Verlauf des Leistungstests weiter zunahm. Die Reaktionszeit der Anwendung wuchs in der ersten Hälfte des Tests langsamer und in der zweiten Hälfte schneller (siehe Abb. 4.a). In der ersten Hälfte des Tests korrelierte das Wachstum der Antwortzeit mit der Gesamtzeit, die Anwendungs-Threads im BLOCKED-Zustand verbracht haben (siehe Abb. 4.b). In der zweiten Hälfte des Tests korrelierte das Wachstum der Antwortzeit mit der Anzahl der Anwendungsthreads im Status PARKED. Die vom Load Test JVM Threads Monitor erfassten Stack-Traces enthielten die Details: einer zeigte auf a synchronized Block, der für übermäßige Zeit im BLOCKED-Zustand verantwortlich war. Der andere zeigte auf die verwendeten Codezeilen java.util.concurrent.locks Klassen für die Synchronisation, die dafür verantwortlich waren, dass Threads im Status PARKED gehalten wurden. Nachdem diese Codebereiche optimiert wurden, wurden beide Leistungsprobleme behoben.

Beispiel 2: Gelegentliche Spitzen in der Anwendungsantwortzeit

Der Lasttest-JVM-Thread-Monitor kann sehr hilfreich sein, um Details zu seltenen Thread-Problemen zu erfassen, insbesondere wenn Ihre Leistungstests automatisiert sind und regelmäßig ausgeführt werden *. Die Diagramme in Abb. 5 zeigen eine Produktions-REST-API-Anwendung mit intermittierenden Spitzen bei durchschnittlichen und maximalen Antwortzeiten (siehe Abb. 5.a).

Solche Spitzen in der Anwendungsantwortzeit können häufig durch eine suboptimale JVM-Garbage-Collector-Konfiguration verursacht werden. In diesem Fall weist jedoch eine korrelierende Spitze im BlockedTime-Monitor (siehe Abb. 5.b) auf die Thread-Synchronisation als Ursache des Problems hin. Der BlockedThreads-Monitor hilft hier noch mehr, indem er die Stapelspuren der blockierten und blockierenden Threads erfasst. Es ist wichtig, den Unterschied zwischen den Monitoren BlockedTime und BlockedThreads zu verstehen.

Der BlockedTime-Monitor zeigt die akkumulierte Zeit an, die JVM-Threads zwischen den Monitoraufrufen im Status BLOCKED verbracht haben, während der BlockedThreads-Monitor regelmäßig Snapshots von JVM-Threads erstellt und in diesen Snapshots nach blockierten Threads sucht. Aus diesem Grund erkennt der BlockedTime-Monitor die Blockierung von Threads zuverlässiger, weist Sie jedoch nur darauf hin, dass Probleme mit der Blockierung von Threads vorliegen. Der BlockedThreads-Monitor, der regelmäßige Thread-Snapshots erstellt, kann einige Thread-Blockierungsereignisse übersehen. Wenn er jedoch solche Ereignisse erfasst, liefert er detaillierte Informationen darüber, was die Blockierung verursacht. Aus diesem Grund ist es eine Frage der Statistik, ob ein BlockedThreads-Monitor die codebezogenen Details eines blockierten Status erfasst oder nicht. Wenn Ihre Leistungstests jedoch regelmäßig ausgeführt werden, wird das BlockedThreads-Diagramm bald einen Spitzenwert aufweisen (siehe Abb 5.c), was bedeutet, dass blockierte und blockierende Thread-Details erfasst wurden. Diese Details verweisen auf die Codezeilen, die für die seltenen Spitzen in der Antwortzeit der Anwendung verantwortlich sind.

 

Erstellen von Steuerelementen für die Leistungsregression

Der Lasttest-JVM-Thread-Monitor ist nicht nur ein effektives Diagnosetool, sondern kann auch zum Erstellen von Steuerelementen für die Leistungsregression für Thread-bezogene Probleme verwendet werden. Nachdem Sie ein solches Leistungsproblem entdeckt und behoben haben, erstellen Sie einen Leistungsregressionstest dafür. Der Test besteht aus einem vorhandenen oder einem neuen Leistungstestlauf und einer neuen Regressionskontrolle. Im Falle eines Parasoft-Auslastungstests wäre dies eine QoS-Überwachungsmetrik für einen relevanten JVM-Threads-Überwachungskanal. Erstellen Sie beispielsweise für das in Beispiel 1 (Abb. 4) beschriebene Problem eine Lasttest-QoS-Monitor-Metrik, die die Zeit überprüft, die Anwendungsthreads im Status BLOCKED verbracht haben, und eine andere Metrik, die die Anzahl der Threads im Status PARKED überprüft. Es ist immer eine gute Idee, benannte Threads in Ihrer Java-Anwendung zu erstellen. Auf diese Weise können Sie Steuerelemente für die Leistungsregression auf einen namengefilterten Satz von Threads anwenden.

Verwenden von Java Threads Monitor in automatisierten Leistungstests

Die folgende Tabelle enthält eine Zusammenfassung der zu verwendenden Threads Monitor-Kanäle und wann:

Threads-Monitor-Kanal Wann zu verwenden
Deadlock-Threads
ÜberwachenDeadlockedThreads
Immer. Deadlocks sind wohl die schwerwiegendsten Thread-Probleme, die die Anwendungsfunktionalität vollständig beeinträchtigen könnten.
Blockierte Threads
Blockierte Zeit
Sperrverhältnis
Anzahl blockiert
Immer. Übermäßige Zeit im BLOCKED-Status oder die Anzahl der BLOCKED-Threads führt normalerweise zu Leistungseinbußen. Überwachen Sie mindestens einen dieser Parameter. Wird auch für Steuerelemente zur Leistungsregression verwendet.
GeparkteThreads Immer. Eine übermäßige Anzahl von Threads im Status PARKED kann auf eine missbräuchliche Verwendung der Klassen java.util.concurrent.locks und andere Threading-Probleme hinweisen. Wird auch für Steuerelemente zur Leistungsregression verwendet.
GesamtThreads Häufig. Verwenden Sie diese Option, um die Anzahl der Threads in BLOCKED, PARKED oder anderen Zuständen mit der Gesamtzahl der Threads zu vergleichen. Wird auch für Steuerelemente zur Leistungsregression verwendet.
SchlafenThreads
WartenThreads
Wartezeit
Wartequote
WaitingCount
Gelegentlich. Verwendung für Leistungsregressionskontrollen in Bezug auf diese Zustände und für Erkundungstests.
NeueThreads
Unbekannte Themen
Selten. Verwendung für Steuerelemente zur Leistungsregression in Bezug auf diese Thread-Zustände.

Fazit

Der JVM-Thread-Monitor von Parasoft ist ein effektives Diagnosetool zum Erkennen von Thread-bezogenen JVM-Leistungsproblemen sowie zum Erstellen erweiterter Steuerelemente für die Leistungsregression. In Kombination mit SOAtestMit dem Load Test Continuum von JVM Threads Monitor können Sie den Schritt zur Reproduktion von Leistungsproblemen vermeiden, indem Sie relevante Thread-Details aufzeichnen, die auf die Codezeilen verweisen, die für eine schlechte Leistung verantwortlich sind, und Sie können sowohl die Anwendungsleistung als auch die Entwickler- und QS-Produktivität verbessern.

 

Neuer Handlungsaufruf

Geschrieben von

Sergej Baranow

Sergei ist Principal Software Engineer bei Parasoft und konzentriert sich auf Last- und Leistungstests innerhalb von Parasoft SOAtest.

Erhalten Sie die neuesten Nachrichten und Ressourcen zum Testen von Software sofort.