JVM Profiler: Un instrument open source pentru urmărirea aplicațiilor JVM distribuite la scară largă | Uber Engineering Blog

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

Cadrele de calcul precum Apache Spark au fost adoptate pe scară largă pentru a construi aplicații de date pe scară largă. Pentru Uber, datele se află în centrul procesului de luare a deciziilor strategice și de dezvoltare a produselor. Pentru a ne ajuta să valorificăm mai bine aceste date, gestionăm implementări masive de Spark în birourile noastre globale de inginerie.

În timp ce Spark face ca tehnologia de date să fie mai accesibilă, dimensionarea corectă a resurselor alocate aplicațiilor Spark și optimizarea eficienței operaționale a infrastructurii noastre de date necesită informații mai fine despre aceste sisteme, și anume modelele lor de utilizare a resurselor.

Pentru a extrage aceste modele fără a modifica codul utilizatorului, am construit și am deschis JVM Profiler, un profiler distribuit pentru a colecta parametrii de performanță și de utilizare a resurselor și a-i servi pentru analize ulterioare. Deși construit pentru Spark, implementarea sa generică îl face aplicabil la orice serviciu sau aplicație bazată pe mașina virtuală Java (JVM). Citiți mai departe pentru a afla cum utilizează Uber acest instrument pentru a face profilul aplicațiilor noastre Spark, precum și cum îl puteți utiliza pentru propriile sisteme.

Provocări de profilare

În fiecare zi, Uber susține zeci de mii de aplicații care rulează pe mii de mașini. Pe măsură ce stiva noastră tehnologică a crescut, ne-am dat seama rapid că soluția noastră existentă de profilare și optimizare a performanței nu va putea să ne satisfacă nevoile. În special, doream să avem capacitatea de a:

Corelați metricele pe un număr mare de procese la nivel de aplicație

Într-un mediu distribuit, mai multe aplicații Spark rulează pe același server și fiecare aplicație Spark are un număr mare de procese (de exemplu, mii de executori) care rulează pe mai multe servere, așa cum este ilustrat în Figura 1:

Figura 1. Într-un mediu distribuit, aplicațiile Spark rulează pe mai multe servere.

Instrumentele noastre existente puteau monitoriza doar parametrii la nivel de server și nu măsurau parametrii pentru aplicații individuale. Aveam nevoie de o soluție care să colecteze metrici pentru fiecare proces și să le coreleze între procesele pentru fiecare aplicație. În plus, nu știm când vor fi lansate aceste procese și cât timp vor dura. Pentru a putea colecta metrici în acest mediu, profilerul trebuie să fie lansat automat cu fiecare proces.

Faceți ca colectarea metricilor să nu fie intruzivă pentru codul arbitrar al utilizatorului

În implementările lor actuale, bibliotecile Spark și Apache Hadoop nu exportă metrici de performanță; cu toate acestea, există adesea situații în care avem nevoie să colectăm aceste metrici fără a modifica codul utilizatorului sau al cadrului. De exemplu, dacă ne confruntăm cu o latență ridicată pe un NameNode al Sistemului de fișiere distribuite Hadoop (HDFS), dorim să verificăm latența observată de fiecare aplicație Spark pentru a ne asigura că aceste probleme nu au fost reproduse. Deoarece codurile clienților NameNode sunt încorporate în biblioteca noastră Spark, este greoi să modificăm codul sursă al acesteia pentru a adăuga această măsurătoare specifică. Pentru a ține pasul cu creșterea perpetuă a infrastructurii noastre de date, trebuie să putem efectua măsurătorile oricărei aplicații în orice moment și fără a face modificări de cod. În plus, implementarea unui proces de colectare a metricilor mai puțin intruziv ne-ar permite să injectăm dinamic codul în metodele Java în timpul încărcării.

Introducerea JVM Profiler

Pentru a aborda aceste două provocări, am construit și am deschis sursa noastră JVM Profiler. Există unele instrumente open source existente, cum ar fi statsd-jvm-profiler de la Etsy, care ar putea colecta măsurători la nivelul aplicațiilor individuale, dar acestea nu oferă capacitatea de a injecta dinamic cod în binarele Java existente pentru a colecta măsurători. Inspirați de unele dintre aceste instrumente, am construit profilerul nostru cu și mai multe capabilități, cum ar fi profilarea arbitrară a metodelor/argumentelor Java.

Ce face JVM Profiler?

Profilerul JVM este compus din trei caracteristici cheie care facilitează colectarea de măsurători de performanță și de utilizare a resurselor, iar apoi servesc aceste măsurători (de exemplu, Apache Kafka) către alte sisteme pentru analize ulterioare:

  • Un agent Java: Prin încorporarea unui agent Java în profilerul nostru, utilizatorii pot colecta diverse măsurători (de exemplu, utilizarea CPU/memorie) și urme de stivă pentru procesele JVM într-un mod distribuit.
  • Capacități avansate de profilare: Profilatorul nostru JVM ne permite să urmărim metode și argumente Java arbitrare în codul utilizatorului fără a face modificări reale ale codului. Această caracteristică poate fi utilizată pentru a urmări latența apelurilor RPC HDFS NameNode pentru aplicațiile Spark și pentru a identifica apelurile lente ale metodelor. De asemenea, poate urmări traseele fișierelor HDFS pe care fiecare aplicație Spark le citește sau le scrie pentru a identifica fișierele fierbinți în vederea optimizării ulterioare.
  • Raportarea analizei datelor: La Uber, folosim profilerul pentru a raporta metricile la subiectele Kafka și la tabelele Apache Hive, ceea ce face ca analiza datelor să fie mai rapidă și mai ușoară.

Cazuri de utilizare tipice

Profilerul nostru JVM suportă o varietate de cazuri de utilizare, mai ales făcând posibilă instrumentarea codului Java arbitrar. Utilizând o simplă modificare a configurației, JVM Profiler se poate atașa la fiecare executor dintr-o aplicație Spark și poate colecta metrici de timp de execuție a metodelor Java. Mai jos, abordăm câteva dintre aceste cazuri de utilizare:

  • Executor de dimensiuni corecte: Utilizăm măsurătorile de memorie de la JVM Profiler pentru a urmări utilizarea reală a memoriei pentru fiecare executor, astfel încât să putem seta valoarea corectă pentru argumentul Spark “executor-memory”.
  • Monitorizăm latența RPC a HDFS NameNode: Facem profilul metodelor din clasa org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB într-o aplicație Spark și identificăm latențe mari la apelurile NameNode. Monitorizăm mai mult de 50 de mii de aplicații Spark în fiecare zi, cu câteva miliarde de astfel de apeluri RPC.
  • Monitorizați evenimentele abandonate de driver: Profilăm metode precum org.apache.spark.scheduler.LiveListenerBus.onDropEvent pentru a urmări situațiile în timpul cărora coada de evenimente a driverului Spark devine prea lungă și renunță la evenimente.
  • Trace data lineage: Profilăm argumentele privind traseul fișierelor pe metoda org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getBlockLocations și org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.addBlock pentru a urmări ce fișiere sunt citite și scrise de aplicația Spark.

Detalii de implementare și extensibilitate

Pentru a face ca implementarea să fie cât mai transparentă, JVM Profiler are un design foarte simplu și extensibil. Oamenii pot adăuga cu ușurință implementări suplimentare ale profilerului pentru a colecta mai multe măsurători și, de asemenea, pot implementa propriii raportori personalizați pentru a trimite măsurători către diferite sisteme pentru analiza datelor.

Figura 2. Profilatorul nostru JVM este compus din mai mulți profileri diferiți care măsoară metrici specifice legate de utilizarea și performanța JVM.

Codul profilatorului JVM este încărcat într-un proces Java prin intermediul unui argument al agentului Java odată ce procesul pornește. Acesta este format din trei părți principale:

  • Transformatorul de fișiere de clasă: instrumentează bytecode-ul metodelor Java în interiorul procesului pentru a face profilul codului arbitrar al utilizatorului și pentru a salva măsurătorile într-un buffer intern de măsurători.
  • Metric Profilers
    • CPU/Memory Profiler: colectează metricile de utilizare a CPU/Memory prin JMX și le trimite către raportori.
    • Method Duration Profiler: citește metricele privind durata metodei (latență) din bufferul de metrice și le trimite raportorilor.
    • Method Argument Profiler: citește valorile argumentelor metodei din bufferul de măsurători și le trimite raportorilor.
  • Reporterii
    • Console Reporter: scrie metricile în ieșirea consolei.
    • Kafka Reporter: trimite metricile către subiectele Kafka.

Cum să extindem JVM Profiler pentru a trimite măsurători prin intermediul unui reporter personalizat

Utilizatorii ar putea să își implementeze proprii reporteri și să îi specifice cu opțiunea -javaagent, cum ar fi:

java

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

Integrarea cu infrastructura de date Uber

Figura 3. JVM Profiler-ul nostru se integrează cu sistemul de infrastructură de date al Uber.

Am integrat metricile JVM Profiler-ului nostru cu infrastructura internă de date a Uber pentru a permite:

  1. Analiza datelor la nivel de cluster: Metricele sunt mai întâi alimentate în Kafka și ingerate în HDFS, apoi utilizatorii le interoghează cu Hive/Presto/Spark.
  2. Depanarea în timp real a aplicațiilor Spark: Utilizăm Flink pentru a agrega date pentru o singură aplicație în timp real și scriem în baza noastră de date MySQL, apoi utilizatorii pot vizualiza metricile printr-o interfață bazată pe web.

Utilizarea JVM Profiler

Mai jos, furnizăm instrucțiuni despre cum să folosim JVM Profiler-ul nostru pentru a urmări o aplicație Java simplă:

În primul rând, clonăm prin git proiectul:

Primul rând, construim proiectul executând următoarea comandă:

În continuare, apelăm fișierul JAR rezultat al construcției (de exemplu, target/jvm-profiler-0.0.5.jar) și rulăm aplicația în interiorul JVM Profiler folosind următoarea comandă:

Comanda rulează aplicația Java de probă și raportează metricile de performanță și de utilizare a resurselor acesteia în consola de ieșire. De exemplu:

Profilatorul poate, de asemenea, să trimită măsurătorile către un subiect Kafka prin intermediul unei comenzi precum următoarea:

Utilizați profilatorul pentru a realiza profilul aplicației Spark

Acum, haideți să parcurgem modul de rulare a profilatorului cu aplicația Spark.

Să presupunem că avem deja un cluster HDFS, încărcăm fișierul JVM Profiler JAR în HDFS-ul nostru:

Apoi folosim linia de comandă spark-submit pentru a lansa aplicația Spark cu profilerul:

Exemple de interogare a metricilor

La Uber, trimitem metricile noastre către subiecte Kafka și programăm conductele de date de fundal pentru a le ingera automat în tabelele Hive. Utilizatorii pot configura conducte similare și pot folosi SQL pentru a interoga metricele profilerului. De asemenea, își pot scrie proprii reporteri pentru a trimite metricile către o bază de date SQL, cum ar fi MySQL, și a le interoga de acolo. Mai jos este un exemplu de schemă a unui tabel Hive:

Mai jos, oferim un exemplu de rezultat la rularea unei interogări SQL anterioare, care arată metricile de memorie și CPU pentru fiecare proces pentru executorii Spark:

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

Rezultate și pași următori

Am aplicat JVM Profiler la una dintre cele mai mari aplicații Spark de la Uber (care utilizează peste 1.000 de executori) și, în acest proces, am redus alocarea de memorie pentru fiecare executor cu 2GB, trecând de la 7GB la 5GB. Numai pentru această aplicație Spark, am economisit 2 TB de memorie.

Am aplicat, de asemenea, JVM Profiler la toate aplicațiile Hive on Spark din cadrul Uber și am găsit câteva oportunități de îmbunătățire a eficienței utilizării memoriei. Figura 3, de mai jos, prezintă un rezultat pe care l-am găsit: aproximativ 70 % dintre aplicațiile noastre au utilizat mai puțin de 80 % din memoria alocată. Constatările noastre au indicat că am putea aloca mai puțină memorie pentru majoritatea acestor aplicații și să creștem utilizarea memoriei cu 20 la sută.

Figura 3. Profilatorul nostru JVM a identificat că 70 la sută dintre aplicații foloseau mai puțin de 80 la sută din memoria alocată.

În timp ce continuăm să ne dezvoltăm soluția, așteptăm cu nerăbdare reduceri suplimentare de memorie în toate JVM-urile noastre.

JVM Profiler este un proiect open source de sine stătător și îi invităm și pe alți dezvoltatori să folosească acest instrument și să contribuie (de exemplu, să trimită cereri pull)!

Echipa noastră de inginerie Big Data din Seattle, Palo Alto și San Francisco lucrează la instrumente și sisteme pentru a extinde întregul ecosistem Hadoop, inclusiv HDFS, Hive, Presto și Spark. Construim tehnologii pe această familie de software open source pentru a ne ajuta să luăm decizii de afaceri mai bune, bazate pe date. Dacă acest lucru vi se pare atrăgător, consultați oportunitățile noastre de angajare și luați în considerare posibilitatea de a vă alătura echipei noastre!

Credit foto Header: Seahorse, Roatan, Honduras, de Conor Myhrvold.

Înscrieți-vă la newsletter-ul nostru pentru a fi la curent cu cele mai recente inovații de la Uber Engineering.

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.