Profilatore JVM: Uno strumento open source per tracciare applicazioni JVM distribuite su scala | Uber Engineering Blog

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

Computing frameworks come Apache Spark sono stati ampiamente adottati per costruire applicazioni di dati su larga scala. Per Uber, i dati sono al centro del processo decisionale strategico e dello sviluppo dei prodotti. Per aiutarci a sfruttare meglio questi dati, gestiamo implementazioni massicce di Spark nei nostri uffici di ingegneria globali.

Mentre Spark rende la tecnologia dei dati più accessibile, il giusto dimensionamento delle risorse assegnate alle applicazioni Spark e l’ottimizzazione dell’efficienza operativa della nostra infrastruttura di dati richiedono approfondimenti più precisi su questi sistemi, in particolare sui loro modelli di utilizzo delle risorse.

Per estrarre questi modelli senza modificare il codice utente, abbiamo costruito e reso open source JVM Profiler, un profiler distribuito per raccogliere le prestazioni e le metriche di utilizzo delle risorse e servire per ulteriori analisi. Anche se costruito per Spark, la sua implementazione generica lo rende applicabile a qualsiasi servizio o applicazione basata su Java virtual machine (JVM). Continuate a leggere per sapere come Uber usa questo strumento per profilare le nostre applicazioni Spark, e come usarlo per i vostri sistemi.

Sfide di profilazione

Ogni giorno, Uber supporta decine di migliaia di applicazioni in esecuzione su migliaia di macchine. Man mano che il nostro stack tecnologico cresceva, ci siamo subito resi conto che la nostra soluzione esistente di profilazione e ottimizzazione delle prestazioni non sarebbe stata in grado di soddisfare le nostre esigenze. In particolare, volevamo la capacità di:

Correlare le metriche su un gran numero di processi a livello di applicazione

In un ambiente distribuito, più applicazioni Spark vengono eseguite sullo stesso server e ogni applicazione Spark ha un gran numero di processi (ad esempio migliaia di executor) in esecuzione su molti server, come illustrato nella Figura 1:

Figura 1. In un ambiente distribuito, le applicazioni Spark vengono eseguite su più server.

I nostri strumenti esistenti potevano monitorare solo le metriche a livello di server e non misuravano le metriche delle singole applicazioni. Avevamo bisogno di una soluzione che potesse raccogliere le metriche per ogni processo e correlarle tra i processi per ogni applicazione. Inoltre, non sappiamo quando questi processi verranno lanciati e quanto tempo impiegheranno. Per essere in grado di raccogliere le metriche in questo ambiente, il profiler deve essere lanciato automaticamente con ogni processo.

Rendere la raccolta delle metriche non intrusiva per il codice utente arbitrario

Nelle loro attuali implementazioni, le librerie Spark e Apache Hadoop non esportano le metriche delle prestazioni; tuttavia, ci sono spesso situazioni in cui abbiamo bisogno di raccogliere queste metriche senza cambiare il codice utente o del framework. Per esempio, se sperimentiamo una latenza elevata su un NameNode di Hadoop Distributed File System (HDFS), vogliamo controllare la latenza osservata da ogni applicazione Spark per assicurarci che questi problemi non siano stati replicati. Poiché i codici dei client NameNode sono incorporati nella nostra libreria Spark, è complicato modificare il codice sorgente per aggiungere questa metrica specifica. Per tenere il passo con la crescita perpetua della nostra infrastruttura di dati, dobbiamo essere in grado di prendere le misure di qualsiasi applicazione in qualsiasi momento e senza apportare modifiche al codice. Inoltre, l’implementazione di un processo di raccolta delle metriche più non intrusivo ci permetterebbe di iniettare dinamicamente il codice nei metodi Java durante il tempo di caricamento.

Introduzione di JVM Profiler

Per affrontare queste due sfide, abbiamo costruito e reso open source il nostro JVM Profiler. Ci sono alcuni strumenti open source esistenti, come statsd-jvm-profiler di Etsy, che potrebbero raccogliere metriche a livello di singola applicazione, ma non forniscono la capacità di iniettare dinamicamente codice nel binario Java esistente per raccogliere metriche. Ispirati da alcuni di questi strumenti, abbiamo costruito il nostro profilatore con ancora più capacità, come il profiling di metodi/argomenti Java arbitrari.

Cosa fa il profilatore JVM?

Il profilatore JVM è composto da tre caratteristiche chiave che rendono più facile raccogliere metriche sulle prestazioni e sull’uso delle risorse, e poi servire queste metriche (ad esempio Apache Kafka) ad altri sistemi per ulteriori analisi:

  • Un agente java: Incorporando un agente Java nel nostro profiler, gli utenti possono raccogliere varie metriche (ad esempio l’utilizzo di CPU/memoria) e tracce di stack per i processi JVM in modo distribuito.
  • Capacità di profiling avanzate: Il nostro Profiler JVM ci permette di tracciare metodi e argomenti Java arbitrari nel codice utente senza apportare modifiche al codice stesso. Questa funzione può essere usata per tracciare la latenza delle chiamate RPC di HDFS NameNode per le applicazioni Spark e identificare le chiamate lente dei metodi. Può anche tracciare i percorsi dei file HDFS che ogni applicazione Spark legge o scrive per identificare i file caldi per ulteriori ottimizzazioni.
  • Segnalazione di analisi dei dati: In Uber, usiamo il profiler per riportare le metriche agli argomenti di Kafka e alle tabelle di Apache Hive, rendendo l’analisi dei dati più facile e veloce.

Casi d’uso tipici

Il nostro profilatore JVM supporta una varietà di casi d’uso, in particolare rende possibile strumentare codice Java arbitrario. Utilizzando una semplice modifica della configurazione, il JVM Profiler può collegarsi ad ogni esecutore in un’applicazione Spark e raccogliere metriche di runtime dei metodi Java. Di seguito, tocchiamo alcuni di questi casi d’uso:

  • Esecutore di dimensioni giuste: Usiamo le metriche di memoria dal JVM Profiler per tracciare l’utilizzo effettivo della memoria per ogni executor in modo da poter impostare il valore corretto per l’argomento “executor-memory” di Spark.
  • Monitorare la latenza RPC di HDFS NameNode: Profiliamo i metodi della classe org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB in un’applicazione Spark e identifichiamo lunghe latenze sulle chiamate NameNode. Monitoriamo più di 50 mila applicazioni Spark ogni giorno con diversi miliardi di chiamate RPC.
  • Monitorare gli eventi di caduta dei driver: Profiliamo metodi come org.apache.spark.scheduler.LiveListenerBus.onDropEvent per tracciare situazioni in cui la coda degli eventi del driver Spark diventa troppo lunga e fa cadere gli eventi.
  • Traccia il lignaggio dei dati: Profiliamo gli argomenti del percorso dei file sul metodo org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getBlockLocations e org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.addBlock per tracciare quali file vengono letti e scritti dall’applicazione Spark.

Dettagli di implementazione ed estensibilità

Per rendere l’implementazione il più semplice possibile, JVM Profiler ha un design molto semplice ed estensibile. Le persone possono facilmente aggiungere ulteriori implementazioni del profilatore per raccogliere più metriche e anche distribuire i propri reporter personalizzati per inviare le metriche a diversi sistemi per l’analisi dei dati.

Figura 2. Il nostro JVM Profiler è composto da diversi profiler che misurano metriche specifiche relative all’uso e alle prestazioni della JVM.

Il codice del JVM Profiler viene caricato in un processo Java tramite un argomento dell’agente Java all’avvio del processo. Consiste di tre parti principali:

  • Class File Transformer: strumenta il bytecode dei metodi Java all’interno del processo per profilare il codice utente arbitrario e salvare le metriche in un buffer interno.
  • Metric Profiler
    • CPU/Memory Profiler: raccoglie le metriche di utilizzo della CPU/Memoria tramite JMX e le invia ai reporter.
    • Method Duration Profiler: legge le metriche di durata del metodo (latenza) dal buffer delle metriche e le invia ai reporter.
    • Method Argument Profiler: legge i valori degli argomenti del metodo dal buffer delle metriche e li invia ai reporter.
  • Reporter
    • Console Reporter: scrive le metriche nell’output della console.
    • Kafka Reporter: invia le metriche agli argomenti Kafka.

Come estendere il JVM Profiler per inviare metriche tramite un reporter personalizzato

Gli utenti potrebbero implementare i propri reporter e specificarli con l’opzione -javaagent, come:

java

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

Integrazione con l’infrastruttura dati di Uber

Figura 3. Il nostro JVM Profiler si integra con il sistema dell’infrastruttura dati di Uber.

Abbiamo integrato le metriche del nostro JVM Profiler con l’infrastruttura dati interna di Uber per consentire:

  1. Analisi dei dati a livello di cluster: Le metriche sono prima alimentate a Kafka e ingerite in HDFS, poi gli utenti le interrogano con Hive/Presto/Spark.
  2. Debug dell’applicazione Spark in tempo reale: Usiamo Flink per aggregare i dati per una singola applicazione in tempo reale e scrivere nel nostro database MySQL, poi gli utenti possono visualizzare le metriche tramite un’interfaccia web-based.

Utilizzando il JVM Profiler

Di seguito, forniamo istruzioni su come utilizzare il nostro JVM Profiler per tracciare una semplice applicazione Java:

Prima di tutto, cloniamo il progetto con git:

Costruiamo il progetto eseguendo il seguente comando:

Poi, chiamiamo il file JAR del risultato della costruzione (ad esempio target/jvm-profiler-0.0.5.jar) ed eseguiamo l’applicazione all’interno di JVM Profiler utilizzando il seguente comando:

Il comando esegue l’applicazione Java di esempio e riporta le sue prestazioni e le metriche di utilizzo delle risorse nella console di output. Per esempio:

Il profiler può anche inviare le metriche a un topic Kafka tramite un comando come il seguente:

Utilizza il profiler per fare il profilo dell’applicazione Spark

Ora, vediamo come eseguire il profiler con l’applicazione Spark.

Assumendo di avere già un cluster HDFS, carichiamo il file JAR di JVM Profiler sul nostro HDFS:

Poi usiamo la riga di comando spark-submit per lanciare l’applicazione Spark con il profiler:

Esempi di query metriche

A Uber, inviamo le nostre metriche agli argomenti Kafka e programmiamo delle pipeline di dati in background per inserirle automaticamente nelle tabelle Hive. Gli utenti possono impostare pipeline simili e usare SQL per interrogare le metriche del profiler. Possono anche scrivere i propri reporter per inviare le metriche a un database SQL come MySQL e interrogarle da lì. Di seguito è riportato un esempio di schema di una tabella Hive:

Di seguito, offriamo un risultato di esempio durante l’esecuzione di una precedente query SQL, che mostra le metriche di memoria e CPU per ogni processo per gli esecutori Spark:

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

Risultati e prossimi passi

Abbiamo applicato il JVM Profiler a una delle più grandi applicazioni Spark di Uber (che usa più di 1.000 executor), e nel processo abbiamo ridotto l’allocazione di memoria per ogni executor di 2GB, passando da 7GB a 5GB. Solo per questa applicazione Spark, abbiamo risparmiato 2TB di memoria.

Abbiamo anche applicato il JVM Profiler a tutte le applicazioni Hive on Spark all’interno di Uber, e abbiamo trovato alcune opportunità per migliorare l’efficienza dell’uso della memoria. La figura 3, qui sotto, mostra un risultato che abbiamo trovato: circa il 70% delle nostre applicazioni ha utilizzato meno dell’80% della memoria allocata. I nostri risultati indicano che potremmo allocare meno memoria per la maggior parte di queste applicazioni e aumentare l’utilizzo della memoria del 20%.

Figura 3. Il nostro JVM Profiler ha identificato che il 70 per cento delle applicazioni stava usando meno dell’80 per cento della memoria allocata.

Continuando a far crescere la nostra soluzione, ci aspettiamo un’ulteriore riduzione della memoria nelle nostre JVM.

JVM Profiler è un progetto open source autonomo e diamo il benvenuto ad altri sviluppatori che utilizzino questo strumento e contribuiscano (ad esempio presentando richieste di pull)!

Il nostro team di Big Data Engineering a Seattle, Palo Alto e San Francisco sta lavorando su strumenti e sistemi per espandere l’intero ecosistema Hadoop, compresi HDFS, Hive, Presto e Spark. Costruiamo tecnologie in cima a questa famiglia di software open source per aiutarci a prendere decisioni di business migliori e basate sui dati. Se questo ti sembra interessante, dai un’occhiata alle nostre opportunità di lavoro e considera la possibilità di unirti al nostro team!

Photo Credit Header: Seahorse, Roatan, Honduras di Conor Myhrvold.

Iscriviti alla nostra newsletter per rimanere aggiornato sulle ultime novità di Uber Engineering.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.