JVM Profiler: An Open Source Tool for Tracing Distributed JVM Applications at Scale | Uber Engineering Blog

JVM Profiler: An Open Source Tool for Tracing Distributed JVM Applications at Scale
954 Shares

Computing-Frameworks wie Apache Spark sind weit verbreitet, um groß angelegte Datenanwendungen zu erstellen. Für Uber sind Daten das Herzstück der strategischen Entscheidungsfindung und Produktentwicklung. Damit wir diese Daten besser nutzen können, verwalten wir umfangreiche Spark-Implementierungen in unseren weltweiten Entwicklungsbüros.

Während Spark die Datentechnologie zugänglicher macht, erfordert die richtige Dimensionierung der den Spark-Anwendungen zugewiesenen Ressourcen und die Optimierung der betrieblichen Effizienz unserer Dateninfrastruktur detailliertere Einblicke in diese Systeme, insbesondere in ihre Ressourcennutzungsmuster.

Um diese Muster zu ermitteln, ohne den Benutzercode zu ändern, haben wir den JVM Profiler, einen verteilten Profiler zum Sammeln von Leistungs- und Ressourcennutzungsmetriken und zum Bereitstellen dieser Daten für weitere Analysen, entwickelt und als Open Source bereitgestellt. Obwohl er für Spark entwickelt wurde, kann er dank seiner generischen Implementierung auf jeden Java Virtual Machine (JVM)-basierten Dienst oder jede Anwendung angewendet werden. Lesen Sie weiter, um zu erfahren, wie Uber dieses Tool zum Profilieren seiner Spark-Anwendungen verwendet und wie Sie es für Ihre eigenen Systeme nutzen können.

Profiling-Herausforderungen

Täglich unterstützt Uber Zehntausende von Anwendungen, die auf Tausenden von Maschinen laufen. Als unser Tech-Stack wuchs, wurde uns schnell klar, dass unsere bestehende Lösung zur Leistungsprofilierung und -optimierung unsere Anforderungen nicht erfüllen konnte. Wir wollten vor allem die Möglichkeit haben,:

Korrelieren von Metriken über eine große Anzahl von Prozessen auf Anwendungsebene

In einer verteilten Umgebung laufen mehrere Spark-Anwendungen auf demselben Server und jede Spark-Anwendung hat eine große Anzahl von Prozessen (z. B. Tausende von Executors), die auf vielen Servern laufen, wie in Abbildung 1 dargestellt:

Abbildung 1. In einer verteilten Umgebung laufen Spark-Anwendungen auf mehreren Servern.

Unsere vorhandenen Tools konnten nur Metriken auf Serverebene überwachen und keine Metriken für einzelne Anwendungen messen. Wir brauchten eine Lösung, die Metriken für jeden Prozess sammeln und sie prozessübergreifend für jede Anwendung korrelieren konnte. Außerdem wissen wir nicht, wann diese Prozesse gestartet werden und wie lange sie dauern werden. Um in dieser Umgebung Metriken sammeln zu können, muss der Profiler automatisch mit jedem Prozess gestartet werden.

Machen Sie die Sammlung von Metriken für beliebigen Benutzercode nicht aufdringlich

In ihren aktuellen Implementierungen exportieren Spark- und Apache-Hadoop-Bibliotheken keine Leistungsmetriken; es gibt jedoch häufig Situationen, in denen wir diese Metriken sammeln müssen, ohne den Benutzer- oder Rahmencode zu ändern. Wenn wir beispielsweise eine hohe Latenz auf einem Hadoop Distributed File System (HDFS) NameNode feststellen, möchten wir die von jeder Spark-Anwendung beobachtete Latenz überprüfen, um sicherzustellen, dass diese Probleme nicht repliziert wurden. Da die NameNode-Client-Codes in unsere Spark-Bibliothek eingebettet sind, ist es mühsam, den Quellcode zu ändern, um diese spezifische Metrik hinzuzufügen. Um mit dem ständigen Wachstum unserer Dateninfrastruktur Schritt zu halten, müssen wir in der Lage sein, die Messungen jeder Anwendung jederzeit und ohne Codeänderungen vorzunehmen. Darüber hinaus würde die Implementierung eines nicht-intrusiven Prozesses zur Sammlung von Metriken uns in die Lage versetzen, während der Ladezeit dynamisch Code in Java-Methoden zu injizieren.

Einführung des JVM Profiler

Um diese beiden Herausforderungen zu bewältigen, haben wir unseren JVM Profiler entwickelt und als Open Source zur Verfügung gestellt. Es gibt einige Open-Source-Tools, wie z.B. statsd-jvm-profiler von Etsy, die Metriken auf individueller Anwendungsebene sammeln können, aber sie bieten nicht die Möglichkeit, dynamisch Code in bestehende Java-Binärdateien zu injizieren, um Metriken zu sammeln. Inspiriert von einigen dieser Tools haben wir unseren Profiler mit noch mehr Fähigkeiten ausgestattet, wie z.B. der Erstellung von Profilen beliebiger Java-Methoden und -Argumente.

Was macht der JVM Profiler?

Der JVM Profiler besteht aus drei Schlüsselfunktionen, die das Sammeln von Leistungs- und Ressourcennutzungsmetriken erleichtern und diese Metriken dann anderen Systemen (z.B. Apache Kafka) zur weiteren Analyse bereitstellen:

  • Ein Java-Agent: Durch die Einbindung eines Java-Agenten in unseren Profiler können Benutzer verschiedene Metriken (z. B. CPU-/Speichernutzung) und Stack Traces für JVM-Prozesse auf verteilte Weise sammeln.
  • Erweiterte Profiling-Funktionen: Unser JVM Profiler ermöglicht es, beliebige Java-Methoden und Argumente im Anwendercode zu verfolgen, ohne dass eigentliche Codeänderungen vorgenommen werden. Diese Funktion kann verwendet werden, um die Latenz von HDFS NameNode RPC-Aufrufen für Spark-Anwendungen zu verfolgen und langsame Methodenaufrufe zu identifizieren. Es kann auch die HDFS-Dateipfade verfolgen, die jede Spark-Anwendung liest oder schreibt, um heiße Dateien für weitere Optimierungen zu identifizieren.
  • Datenanalyseberichte: Bei Uber verwenden wir den Profiler, um Metriken an Kafka-Themen und Apache Hive-Tabellen zu melden, was die Datenanalyse schneller und einfacher macht.

Typische Anwendungsfälle

Unser JVM Profiler unterstützt eine Vielzahl von Anwendungsfällen, vor allem ermöglicht er die Instrumentierung von beliebigem Java-Code. Durch eine einfache Konfigurationsänderung kann sich der JVM Profiler an jeden Executor in einer Spark-Anwendung anhängen und Laufzeitmetriken für Java-Methoden sammeln. Im Folgenden gehen wir auf einige dieser Anwendungsfälle ein:

  • Richtige Größe des Executors: Wir verwenden Speichermetriken aus dem JVM Profiler, um die tatsächliche Speichernutzung für jeden Executor zu verfolgen, damit wir den richtigen Wert für das Spark-Argument “executor-memory” festlegen können.
  • Überwachung der HDFS NameNode RPC-Latenz: Wir profilieren Methoden der Klasse org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB in einer Spark-Anwendung und identifizieren lange Latenzen bei NameNode-Aufrufen. Wir überwachen jeden Tag mehr als 50 Tausend Spark-Anwendungen mit mehreren Milliarden solcher RPC-Aufrufe.
  • Überwachen Sie Ereignisse, bei denen der Treiber ausfällt: Wir profilieren Methoden wie org.apache.spark.scheduler.LiveListenerBus.onDropEvent, um Situationen zu verfolgen, in denen die Ereigniswarteschlange des Spark-Treibers zu lang wird und Ereignisse fallen gelassen werden.
  • Trace data lineage: Wir profilieren Dateipfad-Argumente für die Methoden org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getBlockLocations und org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.addBlock, um zu verfolgen, welche Dateien von der Spark-Anwendung gelesen und geschrieben werden.

Implementierungsdetails und Erweiterbarkeit

Um die Implementierung so nahtlos wie möglich zu gestalten, hat der JVM Profiler ein sehr einfaches und erweiterbares Design. Die Benutzer können leicht zusätzliche Profiler-Implementierungen hinzufügen, um mehr Metriken zu sammeln, und auch ihre eigenen benutzerdefinierten Reporter einsetzen, um Metriken an verschiedene Systeme zur Datenanalyse zu senden.

Abbildung 2. Unser JVM Profiler besteht aus mehreren verschiedenen Profilern, die spezifische Metriken in Bezug auf die JVM-Nutzung und -Leistung messen.

Der JVM Profiler-Code wird über ein Java-Agent-Argument in einen Java-Prozess geladen, sobald der Prozess startet. Er besteht aus drei Hauptteilen:

  • Class File Transformer: instrumentiert Java-Methoden-Bytecode innerhalb des Prozesses, um ein Profil von beliebigem Anwendercode zu erstellen und Metriken in einem internen Metrikpuffer zu speichern.
  • Metric Profiler
    • CPU/Memory Profiler: sammelt CPU/Memory-Nutzungsmetriken über JMX und sendet sie an die Berichterstatter.
    • Method Duration Profiler: liest Metriken zur Methodendauer (Latenz) aus dem Metrikpuffer und sendet sie an die Berichterstatter.
    • Method Argument Profiler: Liest Methodenargumentwerte aus dem Metrikpuffer und sendet sie an die Berichterstatter.
  • Reporter
    • Konsolenreporter: schreibt Metriken in die Konsolenausgabe.
    • Kafka Reporter: sendet Metriken an Kafka-Themen.

Wie man den JVM Profiler erweitert, um Metriken über einen benutzerdefinierten Reporter zu senden

Benutzer können ihre eigenen Reporter implementieren und sie mit der Option -javaagent angeben, wie:

java

-javaagent:jvm-profiler-0.0.5.jar=reporter=com.uber.profiling.reporters.CustomReporter

Integration mit der Dateninfrastruktur von Uber

Abbildung 3. Unser JVM Profiler ist in das Dateninfrastruktursystem von Uber integriert.

Wir haben unsere JVM Profiler-Metriken in die interne Dateninfrastruktur von Uber integriert, um Folgendes zu ermöglichen:

  1. Clusterweite Datenanalyse: Metriken werden zunächst in Kafka eingespeist und in HDFS aufgenommen, dann können Nutzer mit Hive/Presto/Spark Abfragen durchführen.
  2. Debugging von Spark-Anwendungen in Echtzeit: Wir verwenden Flink, um Daten für eine einzelne Anwendung in Echtzeit zu aggregieren und in unsere MySQL-Datenbank zu schreiben; anschließend können die Benutzer die Metriken über eine webbasierte Schnittstelle anzeigen.

Verwendung des JVM-Profilers

Nachfolgend finden Sie eine Anleitung, wie Sie unseren JVM-Profiler verwenden können, um eine einfache Java-Anwendung zu verfolgen:

Zuerst klonen wir das Projekt per Git:

Dann erstellen wir das Projekt, indem wir den folgenden Befehl ausführen:

Nächstens rufen wir die JAR-Datei mit dem Erstellungsergebnis auf (z. B. target/jvm-profiler-0.0.5.jar) und führen die Anwendung im JVM Profiler mit dem folgenden Befehl aus:

Der Befehl führt die Java-Beispielanwendung aus und meldet ihre Leistungs- und Ressourcennutzungsmetriken an die Ausgabekonsole. Beispiel:

Der Profiler kann auch Metriken an ein Kafka-Thema über einen Befehl wie den folgenden senden:

Verwenden Sie den Profiler, um ein Profil der Spark-Anwendung zu erstellen

Nun lassen Sie uns durchgehen, wie Sie den Profiler mit der Spark-Anwendung ausführen.

Angenommen, wir haben bereits einen HDFS-Cluster, laden wir die JVM Profiler JAR-Datei in unser HDFS hoch:

Dann verwenden wir die spark-submit-Befehlszeile, um die Spark-Anwendung mit dem Profiler zu starten:

Beispiele für Metrikabfragen

Bei Uber senden wir unsere Metriken an Kafka-Themen und programmieren Hintergrunddaten-Pipelines, um sie automatisch in Hive-Tabellen aufzunehmen. Benutzer können ähnliche Pipelines einrichten und SQL verwenden, um Profiler-Metriken abzufragen. Sie können auch ihre eigenen Reporter schreiben, um die Metriken an eine SQL-Datenbank wie MySQL zu senden und von dort aus abzufragen. Im Folgenden finden Sie ein Beispiel für ein Hive-Tabellenschema:

Nachfolgend finden Sie ein Beispielergebnis für die Ausführung einer früheren SQL-Abfrage, die die Speicher- und CPU-Metriken für jeden Prozess für die Spark-Executors anzeigt:

role processUuid maxHeapMemoryMB avgProcessCpuLoad
executor 6d3aa4ee-4233-4cc7-a628-657e1a87825d 2805.255325 7.61E-11
executor 21f418d1-6d4f-440d-b28a-4eba1a3bb53d 3993.969582 9.56E-11
executor a5da6e8c-149b-4722-8fa8-74a16baabcf8 3154.484474 8.18E-11
Executor a1994e27-59bd-4cda-9de3-745ead954a27 2561.847374 8.58E-11

Ergebnisse und nächste Schritte

Wir haben den JVM Profiler auf eine der größten Spark-Anwendungen von Uber angewandt (die mehr als 1.000 Executors verwendet) und dabei die Speicherzuweisung für jeden Executor um 2 GB reduziert, von 7 GB auf 5 GB. Allein für diese Spark-Anwendung konnten wir 2 TB Speicher einsparen.

Wir wendeten den JVM Profiler auch auf alle Hive on Spark-Anwendungen in Uber an und fanden einige Möglichkeiten zur Verbesserung der Speichernutzungseffizienz. Abbildung 3 zeigt ein Ergebnis: Etwa 70 Prozent unserer Anwendungen nutzten weniger als 80 Prozent des ihnen zugewiesenen Speichers. Unsere Ergebnisse deuten darauf hin, dass wir für die meisten dieser Anwendungen weniger Speicher zuweisen und die Speichernutzung um 20 Prozent erhöhen könnten.

Abbildung 3. Unser JVM Profiler hat festgestellt, dass 70 Prozent der Anwendungen weniger als 80 Prozent des ihnen zugewiesenen Speichers nutzen.

Während wir unsere Lösung weiter ausbauen, freuen wir uns auf eine weitere Reduzierung des Speichers in unseren JVMs.

JVM Profiler ist ein eigenständiges Open-Source-Projekt und wir freuen uns, wenn andere Entwickler dieses Tool nutzen und ebenfalls einen Beitrag leisten (z. B. Pull-Requests einreichen)!

Unser Big-Data-Engineering-Team in Seattle, Palo Alto und San Francisco arbeitet an Tools und Systemen zur Erweiterung des gesamten Hadoop-Ökosystems, einschließlich HDFS, Hive, Presto und Spark. Wir entwickeln Technologien, die auf dieser Familie von Open-Source-Software aufbauen und uns dabei helfen, bessere, datengesteuerte Geschäftsentscheidungen zu treffen. Wenn Sie sich angesprochen fühlen, sehen Sie sich unsere Stellenangebote an und ziehen Sie in Erwägung, unserem Team beizutreten!

Foto Credit Header: Seepferdchen, Roatan, Honduras von Conor Myhrvold.

Abonnieren Sie unseren Newsletter, um über die neuesten Innovationen von Uber Engineering informiert zu werden.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.