JVM Profiler: Una herramienta de código abierto para rastrear aplicaciones JVM distribuidas a escala | Uber Engineering Blog

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

Los marcos de computación como Apache Spark han sido ampliamente adoptados para construir aplicaciones de datos a gran escala. Para Uber, los datos están en el centro de la toma de decisiones estratégicas y el desarrollo de productos. Para ayudarnos a aprovechar mejor estos datos, gestionamos despliegues masivos de Spark en nuestras oficinas globales de ingeniería.

Aunque Spark hace que la tecnología de datos sea más accesible, el dimensionamiento correcto de los recursos asignados a las aplicaciones de Spark y la optimización de la eficiencia operativa de nuestra infraestructura de datos requiere una visión más detallada de estos sistemas, es decir, sus patrones de uso de recursos.

Para extraer estos patrones sin cambiar el código del usuario, construimos y abrimos el JVM Profiler, un perfilador distribuido para recoger el rendimiento y las métricas de uso de recursos y servirlos para su posterior análisis. Aunque está construido para Spark, su implementación genérica lo hace aplicable a cualquier servicio o aplicación basada en la máquina virtual Java (JVM). Sigue leyendo para saber cómo Uber utiliza esta herramienta para perfilar nuestras aplicaciones Spark, así como para saber cómo utilizarla en tus propios sistemas.

Desafíos de los perfiles

Diariamente, Uber soporta decenas de miles de aplicaciones que se ejecutan en miles de máquinas. A medida que nuestra pila tecnológica crecía, nos dimos cuenta rápidamente de que nuestra solución existente de perfilado y optimización del rendimiento no sería capaz de satisfacer nuestras necesidades. En particular, queríamos la capacidad de:

Correlacionar métricas a través de un gran número de procesos a nivel de aplicación

En un entorno distribuido, múltiples aplicaciones Spark se ejecutan en el mismo servidor y cada aplicación Spark tiene un gran número de procesos (por ejemplo, miles de ejecutores) que se ejecutan en muchos servidores, como se ilustra en la Figura 1:

Figura 1. En un entorno distribuido, las aplicaciones Spark se ejecutan en múltiples servidores.

Nuestras herramientas existentes sólo podían monitorizar las métricas a nivel de servidor y no medían las métricas de las aplicaciones individuales. Necesitábamos una solución que pudiera recoger las métricas de cada proceso y correlacionarlas entre los procesos de cada aplicación. Además, no sabemos cuándo se van a lanzar estos procesos ni cuánto tiempo van a tardar. Para poder recopilar métricas en este entorno, el perfilador debe lanzarse automáticamente con cada proceso.

Hacer que la recopilación de métricas no sea intrusiva para el código de usuario arbitrario

En sus implementaciones actuales, las librerías Spark y Apache Hadoop no exportan las métricas de rendimiento; sin embargo, a menudo hay situaciones en las que necesitamos recopilar estas métricas sin cambiar el código de usuario o del framework. Por ejemplo, si experimentamos una alta latencia en un NameNode de Hadoop Distributed File System (HDFS), queremos comprobar la latencia observada desde cada aplicación de Spark para asegurarnos de que estos problemas no se han replicado. Dado que los códigos de cliente de NameNode están incrustados dentro de nuestra biblioteca de Spark, es engorroso modificar su código fuente para añadir esta métrica específica. Para seguir el ritmo del crecimiento perpetuo de nuestra infraestructura de datos, necesitamos poder tomar las medidas de cualquier aplicación en cualquier momento y sin realizar cambios en el código. Además, la implementación de un proceso de recopilación de métricas más no intrusivo nos permitiría inyectar código de forma dinámica en los métodos de Java durante el tiempo de carga.

Presentación de JVM Profiler

Para hacer frente a estos dos desafíos, construimos y abrimos nuestro JVM Profiler. Hay algunas herramientas de código abierto existentes, como statsd-jvm-profiler de Etsy, que podrían recoger métricas a nivel de aplicación individual, pero no proporcionan la capacidad de inyectar dinámicamente código en el binario Java existente para recoger métricas. Inspirado por algunas de estas herramientas, construimos nuestro perfilador con aún más capacidades, como la creación de perfiles de métodos/argumentos arbitrarios de Java.

¿Qué hace el perfilador de la JVM?

El perfilador de la JVM se compone de tres características clave que facilitan la recopilación de métricas de rendimiento y uso de recursos, y luego sirven estas métricas (por ejemplo, Apache Kafka) a otros sistemas para su posterior análisis:

  • Un agente de Java: Al incorporar un agente de Java en nuestro perfilador, los usuarios pueden recopilar varias métricas (por ejemplo, uso de CPU/memoria) y trazas de pila para los procesos JVM de forma distribuida.
  • Capacidades avanzadas de creación de perfiles: Nuestro JVM Profiler nos permite rastrear métodos y argumentos Java arbitrarios en el código del usuario sin hacer ningún cambio de código real. Esta función puede utilizarse para rastrear la latencia de las llamadas RPC de HDFS NameNode para las aplicaciones de Spark e identificar las llamadas a métodos lentos. También puede rastrear las rutas de archivos de HDFS que cada aplicación de Spark lee o escribe para identificar archivos calientes para una mayor optimización.
  • Informes de análisis de datos: En Uber, utilizamos el perfilador para informar de las métricas a los temas de Kafka y a las tablas de Apache Hive, haciendo que el análisis de datos sea más rápido y sencillo.

Casos de uso típicos

Nuestro JVM Profiler soporta una variedad de casos de uso, sobre todo haciendo posible instrumentar código Java arbitrario. Utilizando un simple cambio de configuración, el JVM Profiler puede adjuntar a cada ejecutor en una aplicación Spark y recopilar métricas de tiempo de ejecución de métodos Java. A continuación, tocamos algunos de estos casos de uso:

  • Ejecutor de tamaño correcto: Utilizamos las métricas de memoria del JVM Profiler para rastrear el uso real de la memoria para cada ejecutor, de modo que podamos establecer el valor adecuado para el argumento “executor-memory” de Spark.
  • Monitorizar la latencia RPC de HDFS NameNode: Perfilamos los métodos de la clase org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB en una aplicación Spark e identificamos largas latencias en las llamadas a NameNode. Monitorizamos más de 50 mil aplicaciones Spark cada día con varios miles de millones de llamadas RPC de este tipo.
  • Monitorizar los eventos de caída del driver: Perfilamos métodos como org.apache.spark.scheduler.LiveListenerBus.onDropEvent para rastrear situaciones durante las cuales la cola de eventos del controlador de Spark se alarga demasiado y deja caer eventos.
  • Rastrear el linaje de datos: Perfilamos los argumentos de ruta de archivos en el método org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getBlockLocations y org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.addBlock para rastrear qué archivos lee y escribe la aplicación Spark.

Detalles de implementación y extensibilidad

Para que la implementación sea lo más fluida posible, JVM Profiler tiene un diseño muy simple y extensible. La gente puede añadir fácilmente implementaciones adicionales del perfilador para recoger más métricas y también desplegar sus propios reporteros personalizados para enviar métricas a diferentes sistemas para el análisis de datos.

Figura 2. Nuestro JVM Profiler está compuesto por varios perfiladores diferentes que miden métricas específicas relacionadas con el uso y el rendimiento de la JVM.

El código del JVM Profiler se carga en un proceso Java a través de un argumento del agente Java una vez que el proceso se inicia. Consta de tres partes principales:

  • Transformador de archivos de clase: instrumenta el bytecode de los métodos Java dentro del proceso para perfilar código de usuario arbitrario y guardar las métricas en un buffer de métricas interno.
  • Metric Profilers
    • CPU/Memory Profiler: recoge las métricas de uso de CPU/Memoria a través de JMX y las envía a los reporteros.
    • Method Duration Profiler: lee las métricas de duración del método (latencia) desde el buffer de métricas y las envía a los reporteros.
    • Method Argument Profiler: lee los valores de los argumentos del método desde el buffer de métricas y los envía a los reporteros.
  • Reporteros
    • Reportero de la consola: escribe las métricas en la salida de la consola.
    • Kafka Reporter: envía las métricas a los temas de Kafka.

Cómo extender el JVM Profiler para enviar métricas a través de un reportero personalizado

Los usuarios podrían implementar sus propios reporteros y especificarlos con la opción -javaagent, como:

java

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

Integración con la infraestructura de datos de Uber

Figura 3. Nuestro JVM Profiler se integra con el sistema de infraestructura de datos de Uber.

Integramos nuestras métricas de JVM Profiler con la infraestructura de datos interna de Uber para permitir:

  1. Análisis de datos en todo el clúster: Las métricas se alimentan primero en Kafka y se ingieren en HDFS, luego los usuarios hacen consultas con Hive/Presto/Spark.
  2. Depuración de aplicaciones Spark en tiempo real: Utilizamos Flink para agregar datos para una sola aplicación en tiempo real y escribir en nuestra base de datos MySQL, luego los usuarios pueden ver las métricas a través de una interfaz basada en la web.

Usando el JVM Profiler

A continuación, proporcionamos instrucciones sobre cómo utilizar nuestro JVM Profiler para rastrear una aplicación Java simple:

Primero, clonamos con git el proyecto:

Construimos el proyecto ejecutando el siguiente comando:

A continuación, llamamos al archivo JAR resultante de la construcción (por ejemplo, target/jvm-profiler-0.0.5.jar) y ejecutamos la aplicación dentro del JVM Profiler utilizando el siguiente comando:

El comando ejecuta la aplicación Java de ejemplo e informa de sus métricas de rendimiento y uso de recursos en la consola de salida. Por ejemplo:

El profiler también puede enviar métricas a un tema de Kafka a través de un comando como el siguiente:

Usa el profiler para perfilar la aplicación Spark

Ahora, vamos a recorrer cómo ejecutar el profiler con la aplicación Spark.

Suponiendo que ya tenemos un clúster HDFS, subimos el archivo JAR de JVM Profiler a nuestro HDFS:

Luego utilizamos la línea de comandos spark-submit para lanzar la aplicación Spark con el profiler:

Ejemplos de consulta de métricas

En Uber, enviamos nuestras métricas a temas de Kafka y programamos pipelines de datos en segundo plano para ingerirlas automáticamente en tablas de Hive. Los usuarios pueden configurar tuberías similares y utilizar SQL para consultar las métricas del perfilador. También pueden escribir sus propios reportes para enviar las métricas a una base de datos SQL como MySQL y consultar desde allí. A continuación se muestra un ejemplo de esquema de tabla Hive:

A continuación, ofrecemos un ejemplo de resultado al ejecutar una consulta SQL anterior, que muestra las métricas de memoria y CPU de cada proceso para los ejecutores de Spark:

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

Resultados y próximos pasos

Aplicamos el JVM Profiler a una de las mayores aplicaciones Spark de Uber (que utiliza más de 1.000 ejecutores), y en el proceso, redujimos la asignación de memoria para cada ejecutor en 2GB, pasando de 7GB a 5GB. Sólo para esta aplicación Spark, hemos ahorrado 2TB de memoria.

También aplicamos el JVM Profiler a todas las aplicaciones Hive on Spark dentro de Uber, y encontramos algunas oportunidades para mejorar la eficiencia del uso de la memoria. La figura 3, a continuación, muestra un resultado que encontramos: alrededor del 70 por ciento de nuestras aplicaciones utilizaron menos del 80 por ciento de su memoria asignada. Nuestros hallazgos indicaron que podríamos asignar menos memoria para la mayoría de estas aplicaciones y aumentar la utilización de la memoria en un 20 por ciento.

Figura 3. Nuestro JVM Profiler identificó que el 70 por ciento de las aplicaciones utilizaban menos del 80 por ciento de su memoria asignada.

A medida que continuamos haciendo crecer nuestra solución, esperamos una reducción adicional de la memoria en todas nuestras JVMs.

JVM Profiler es un proyecto de código abierto autónomo y damos la bienvenida a otros desarrolladores para que utilicen esta herramienta y contribuyan (por ejemplo, enviando solicitudes de extracción) también.

Nuestro equipo de ingeniería de Big Data en Seattle, Palo Alto y San Francisco está trabajando en herramientas y sistemas para ampliar todo el ecosistema Hadoop, incluyendo HDFS, Hive, Presto y Spark. Construimos tecnologías sobre esta familia de software de código abierto para ayudarnos a tomar mejores decisiones empresariales basadas en datos. Si esto le parece atractivo, eche un vistazo a nuestras oportunidades de trabajo y considere la posibilidad de unirse a nuestro equipo.

Cabecera de la foto: Seahorse, Roatán, Honduras por Conor Myhrvold.

Suscríbete a nuestro boletín para estar al día de las últimas innovaciones de Uber Engineering.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.