JVM Profiler : Un outil open source pour tracer les applications JVM distribuées à l’échelle | Uber Engineering Blog

JVM Profiler : Un outil open source pour tracer les applications JVM distribuées à l'échelle
954 Shares

Des frameworks de calcul comme Apache Spark ont été largement adoptés pour construire des applications de données à grande échelle. Pour Uber, les données sont au cœur de la prise de décision stratégique et du développement des produits. Pour nous aider à mieux exploiter ces données, nous gérons des déploiements massifs de Spark dans nos bureaux d’ingénierie mondiaux.

Alors que Spark rend la technologie des données plus accessible, le dimensionnement correct des ressources allouées aux applications Spark et l’optimisation de l’efficacité opérationnelle de notre infrastructure de données nécessitent des informations plus fines sur ces systèmes, à savoir leurs schémas d’utilisation des ressources.

Pour exploiter ces schémas sans modifier le code utilisateur, nous avons construit et mis en open sourcing JVM Profiler, un profileur distribué pour collecter des métriques de performance et d’utilisation des ressources et les servir pour une analyse plus approfondie. Bien que conçu pour Spark, sa mise en œuvre générique le rend applicable à n’importe quel service ou application basé sur une machine virtuelle Java (JVM). Lisez la suite pour savoir comment Uber utilise cet outil pour profiler nos applications Spark, ainsi que pour savoir comment l’utiliser pour vos propres systèmes.

Défis de profilage

Au quotidien, Uber prend en charge des dizaines de milliers d’applications exécutées sur des milliers de machines. À mesure que notre pile technologique s’est développée, nous avons rapidement réalisé que notre solution existante de profilage et d’optimisation des performances ne serait pas en mesure de répondre à nos besoins. En particulier, nous voulions avoir la capacité de :

Corréler les métriques sur un grand nombre de processus au niveau de l’application

Dans un environnement distribué, plusieurs applications Spark s’exécutent sur le même serveur et chaque application Spark possède un grand nombre de processus (par exemple, des milliers d’exécuteurs) s’exécutant sur de nombreux serveurs, comme l’illustre la figure 1:

Figure 1. Dans un environnement distribué, les applications Spark s’exécutent sur plusieurs serveurs.

Nos outils existants ne pouvaient surveiller que les métriques au niveau du serveur et ne jaugeaient pas les métriques des applications individuelles. Nous avions besoin d’une solution capable de collecter des métriques pour chaque processus et de les corréler entre les processus pour chaque application. En outre, nous ne savons pas quand ces processus seront lancés et combien de temps ils prendront. Pour pouvoir collecter des métriques dans cet environnement, le profileur doit être lancé automatiquement avec chaque processus.

Rendre la collecte de métriques non intrusive pour le code utilisateur arbitraire

Dans leurs implémentations actuelles, les bibliothèques Spark et Apache Hadoop n’exportent pas de métriques de performance ; cependant, il existe souvent des situations où nous devons collecter ces métriques sans modifier le code utilisateur ou le framework. Par exemple, si nous constatons une latence élevée sur un NameNode Hadoop Distributed File System (HDFS), nous voulons vérifier la latence observée à partir de chaque application Spark pour nous assurer que ces problèmes n’ont pas été répliqués. Comme les codes clients de NameNode sont intégrés à notre bibliothèque Spark, il est difficile de modifier son code source pour ajouter cette mesure spécifique. Pour suivre la croissance perpétuelle de notre infrastructure de données, nous devons être en mesure de prendre les mesures de n’importe quelle application à tout moment et sans apporter de modifications au code. De plus, la mise en œuvre d’un processus de collecte de métriques plus non intrusif nous permettrait d’injecter dynamiquement du code dans les méthodes Java pendant le temps de chargement.

Introducing JVM Profiler

Pour relever ces deux défis, nous avons construit et mis en open source notre JVM Profiler. Il existe certains outils open source existants, comme statsd-jvm-profiler d’Etsy, qui pourraient collecter des métriques au niveau de l’application individuelle, mais ils ne fournissent pas la capacité d’injecter dynamiquement du code dans le binaire Java existant pour collecter des métriques. Inspirés par certains de ces outils, nous avons construit notre profileur avec encore plus de capacités, comme le profilage arbitraire des méthodes/arguments Java.

Que fait le profileur JVM ?

Le profileur JVM est composé de trois fonctionnalités clés qui facilitent la collecte de métriques de performance et d’utilisation des ressources, puis servent ces métriques (par exemple Apache Kafka) à d’autres systèmes pour une analyse plus approfondie :

  • Un agent java : En incorporant un agent Java dans notre profileur, les utilisateurs peuvent collecter diverses métriques (par exemple, l’utilisation du CPU/de la mémoire) et les traces de pile pour les processus JVM de manière distribuée.
  • Capacités de profilage avancées : Notre profileur JVM nous permet de tracer des méthodes et des arguments Java arbitraires dans le code utilisateur sans apporter de modifications réelles au code. Cette fonctionnalité peut être utilisée pour tracer la latence des appels RPC HDFS NameNode pour les applications Spark et identifier les appels de méthode lents. Elle peut également tracer les chemins de fichiers HDFS que chaque application Spark lit ou écrit afin d’identifier les fichiers chauds pour une optimisation supplémentaire.
  • Rapports d’analyse de données : Chez Uber, nous utilisons le profileur pour rapporter les métriques aux sujets Kafka et aux tables Apache Hive, ce qui rend l’analyse des données plus rapide et plus facile.

Cas d’utilisation typiques

Notre JVM Profiler prend en charge une variété de cas d’utilisation, permettant notamment d’instrumenter un code Java arbitraire. À l’aide d’un simple changement de configuration, le JVM Profiler peut s’attacher à chaque exécuteur d’une application Spark et collecter les métriques d’exécution des méthodes Java. Nous abordons ci-dessous certains de ces cas d’utilisation :

  • Dimensionner correctement l’exécuteur : Nous utilisons les métriques de mémoire du JVM Profiler pour suivre l’utilisation réelle de la mémoire pour chaque exécuteur afin de pouvoir définir la valeur appropriée pour l’argument “executor-memory” de Spark.
  • Surveiller la latence RPC du NameNode HDFS : Nous profilons les méthodes sur la classe org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB dans une application Spark et identifions les longues latences sur les appels NameNode. Nous surveillons plus de 50 mille applications Spark chaque jour avec plusieurs milliards de tels appels RPC.
  • Surveiller les événements d’abandon de pilote : Nous profilons des méthodes comme org.apache.spark.scheduler.LiveListenerBus.onDropEvent pour tracer les situations au cours desquelles la file d’attente des événements du pilote Spark devient trop longue et abandonne les événements.
  • Tracer le lignage des données : Nous profilons les arguments de chemin de fichier sur la méthode org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getBlockLocations et org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.addBlock pour tracer quels fichiers sont lus et écrits par l’application Spark.

Détails de mise en œuvre et extensibilité

Pour rendre la mise en œuvre aussi transparente que possible, JVM Profiler a une conception très simple et extensible. Les gens peuvent facilement ajouter des implémentations de profileur supplémentaires pour collecter plus de métriques et également déployer leurs propres rapporteurs personnalisés pour envoyer des métriques à différents systèmes pour l’analyse des données.

Figure 2. Notre profileur JVM est composé de plusieurs profileurs différents qui mesurent des métriques spécifiques liées à l’utilisation et aux performances de la JVM.

Le code du profileur JVM est chargé dans un processus Java via un argument d’agent Java une fois que le processus démarre. Il se compose de trois parties principales :

  • Transformateur de fichier de classe : instrumente le bytecode des méthodes Java à l’intérieur du processus pour profiler un code utilisateur arbitraire et enregistrer les métriques dans un tampon de métrique interne.
  • Profileurs de métriques
    • Profileur de CPU/Mémoire : collecte les métriques d’utilisation de CPU/Mémoire via JMX et les envoie aux rapporteurs.
    • Profileur de durée de méthode : lit les métriques de durée de méthode (latence) à partir du tampon de métriques et les envoie aux rapporteurs.
    • Method Argument Profiler : lit les valeurs des arguments de la méthode à partir du tampon de métriques et les envoie aux rapporteurs.
  • Reporters
    • Reporters de console : écrit les métriques dans la sortie de la console.
    • Kafka Reporter : envoie les métriques aux sujets Kafka.

Comment étendre le JVM Profiler pour envoyer des métriques via un reporter personnalisé

Les utilisateurs pourraient implémenter leurs propres reporters et les spécifier avec l’option -javaagent, comme:

java

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

Intégration avec l’infrastructure de données d’Uber

Figure 3. Notre JVM Profiler s’intègre au système d’infrastructure de données d’Uber.

Nous avons intégré nos métriques JVM Profiler à l’infrastructure de données interne d’Uber pour permettre :

  1. L’analyse des données à l’échelle du cluster : Les métriques sont d’abord alimentées par Kafka et ingérées dans HDFS, puis les utilisateurs interrogent avec Hive/Presto/Spark.
  2. Débogage en temps réel des applications Spark : Nous utilisons Flink pour agréger les données d’une seule application en temps réel et écrire dans notre base de données MySQL, puis les utilisateurs peuvent visualiser les métriques via une interface web.

Utilisation du profileur JVM

Ci-après, nous fournissons des instructions sur la façon d’utiliser notre profileur JVM pour tracer une application Java simple :

Premièrement, nous clonons git le projet:

Puis nous construisons le projet en exécutant la commande suivante:

Puis, nous appelons le fichier JAR de résultat de construction (par exemple, target/jvm-profiler-0.0.5.jar) et exécutons l’application à l’intérieur du JVM Profiler en utilisant la commande suivante:

La commande exécute l’exemple d’application Java et rapporte ses métriques de performance et d’utilisation des ressources sur la console de sortie. Par exemple:

Le profiler peut également envoyer des métriques à un sujet Kafka via une commande comme la suivante:

Utiliser le profiler pour profiler l’application Spark

Maintenant, parcourons comment exécuter le profiler avec l’application Spark.

En supposant que nous avons déjà un cluster HDFS, nous téléchargeons le fichier JAR du profileur JVM sur notre HDFS:

Puis nous utilisons la ligne de commande spark-submit pour lancer l’application Spark avec le profileur:

Exemples de requêtes métriques

À Uber, nous envoyons nos métriques aux sujets Kafka et programmons des pipelines de données en arrière-plan pour les ingérer automatiquement dans les tables Hive. Les utilisateurs peuvent configurer des pipelines similaires et utiliser SQL pour interroger les métriques des profileurs. Ils peuvent également écrire leurs propres rapporteurs pour envoyer les mesures vers une base de données SQL comme MySQL et les interroger à partir de là. Voici un exemple de schéma de table Hive:

Ci-après, nous proposons un exemple de résultat lors de l’exécution d’une requête SQL précédente, qui montre les métriques de mémoire et de CPU pour chaque processus pour les exécuteurs Spark :

role processUuid maxHeapMemoryMB avgProcessCpuLoad
execteur 6d3aa4ee-4233-4cc7-a628-657e1a87825d 2805.255325 7,61E-11
exécuteur 21f418d1-6d4f-440d-b28a-4eba1a3bb53d 3993.969582 9.56E-11
exécuteur a5da6e8c-149b-4722-8fa8-74a16baabcf8 3154.484474 8,18E-11
exécuteur a1994e27-59bd-4cda-9de3-745ead954a27 2561,847374 8.58E-11

Résultats et prochaines étapes

Nous avons appliqué le JVM Profiler à l’une des plus grandes applications Spark d’Uber (qui utilise plus de 1 000 exécuteurs), et dans le processus, nous avons réduit l’allocation de mémoire pour chaque exécuteur de 2 Go, passant de 7 Go à 5 Go. Pour cette seule application Spark, nous avons économisé 2 To de mémoire.

Nous avons également appliqué le JVM Profiler à toutes les applications Hive on Spark à l’intérieur d’Uber, et avons trouvé quelques opportunités d’améliorer l’efficacité de l’utilisation de la mémoire. La figure 3, ci-dessous, montre un résultat que nous avons trouvé : environ 70 % de nos applications utilisaient moins de 80 % de leur mémoire allouée. Nos résultats indiquent que nous pourrions allouer moins de mémoire pour la plupart de ces applications et augmenter l’utilisation de la mémoire de 20 pour cent.

Figure 3. Notre JVM Profiler a identifié que 70 pour cent des applications utilisaient moins de 80 pour cent de leur mémoire allouée.

A mesure que nous continuons à développer notre solution, nous attendons avec impatience une réduction supplémentaire de la mémoire à travers nos JVM.

JVM Profiler est un projet open source autonome et nous invitons d’autres développeurs à utiliser cet outil et à contribuer (par exemple, soumettre des demandes de pull) également !

Notre équipe d’ingénierie Big Data à Seattle, Palo Alto et San Francisco travaille sur des outils et des systèmes pour étendre l’ensemble de l’écosystème Hadoop, y compris HDFS, Hive, Presto et Spark. Nous construisons des technologies au-dessus de cette famille de logiciels open source pour nous aider à prendre de meilleures décisions commerciales basées sur les données. Si cela vous semble attrayant, consultez nos offres d’emploi et envisagez de rejoindre notre équipe !

En-tête de crédit photo : Hippocampe, Roatan, Honduras par Conor Myhrvold.

Souscrivez à notre newsletter pour être au courant des dernières innovations d’Uber Engineering.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.