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
- Corelați metricele pe un număr mare de procese la nivel de aplicație
- Faceți ca colectarea metricilor să nu fie intruzivă pentru codul arbitrar al utilizatorului
- Introducerea JVM Profiler
- Ce face JVM Profiler?
- Cazuri de utilizare tipice
- Detalii de implementare și extensibilitate
- Cum să extindem JVM Profiler pentru a trimite măsurători prin intermediul unui reporter personalizat
- Integrarea cu infrastructura de date Uber
- Utilizarea JVM Profiler
- Utilizați profilatorul pentru a realiza profilul aplicației Spark
- Exemple de interogare a metricilor
- Rezultate și pași următori
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:
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.
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
Am integrat metricile JVM Profiler-ului nostru cu infrastructura internă de date a Uber pentru a permite:
- 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.
- 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ă.
Î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.
.