Desenvolvimento

11 jul, 2018

JVM Profiler: uma ferramenta open source para rastrear aplicativos JVM distribuídos em escala

Publicidade

Frameworks de computação como o Apache Spark foram amplamente adotados para construir aplicativos de dados em larga escala. Para a Uber, os dados estão no centro das decisões estratégicas e do desenvolvimento de produtos.

Para nos ajudar a aproveitar melhor esses dados, gerenciamos implementações massivas do Spark em nossos escritórios globais de engenharia.

Embora o Spark torne a tecnologia de dados mais acessível, o dimensionamento correto dos recursos alocados para os aplicativos Spark e a otimização da eficiência operacional de nossa infraestrutura de dados exigem insights mais detalhados sobre esses sistemas, ou seja, seus padrões de uso de recursos.

Para explorar esses padrões sem alterar o código do usuário, criamos e abrimos o código do JVM Profiler, um profiler distribuído para coletar métricas de desempenho e uso de recursos, e servi-las para análise posterior. Embora construído para o Spark, sua implementação genérica o torna aplicável a qualquer serviço ou aplicativo baseado em Java virtual machine (JVM). Continue lendo para saber como a Uber usa essa ferramenta para criar um perfil de nossos aplicativos Spark, além de como usá-lo em seus próprios sistemas.

Desafios de perfil

Diariamente, a Uber suporta dezenas de milhares de aplicativos em execução em milhares de máquinas. À medida que nossa pilha de tecnologia cresceu, percebemos rapidamente que nossa solução de otimização e perfil de desempenho existente não seria capaz de atender às nossas necessidades. Em particular, queríamos a capacidade de:

Correlacionar métricas em um grande número de processos no nível do aplicativo

Em um ambiente distribuído, vários aplicativos Spark são executados no mesmo servidor, e cada aplicativo Spark possui um grande número de processos (por exemplo, milhares de executores) rodando em vários servidores, conforme ilustrado na Figura 1:

Figura 1. Em um ambiente distribuído, os aplicativos Spark são executados em vários servidores.

Nossas ferramentas existentes só podiam monitorar as métricas no nível do servidor e não mediam as métricas para aplicativos individuais. Precisávamos de uma solução que pudesse coletar métricas para cada processo e correlacioná-las nos processos de cada aplicativo. Além disso, não sabemos quando esses processos serão lançados e quanto tempo levarão. Para poder coletar métricas nesse ambiente, o profiler precisa ser lançado automaticamente com cada processo.

Tornar não-intrusiva a coleta de métricas para código de usuário arbitrário

Em suas implementações atuais, as bibliotecas Spark e Apache Hadoop não exportam métricas de desempenho; no entanto, muitas vezes há situações em que precisamos coletar essas métricas sem alterar o código do usuário ou o do framework. Por exemplo, se tivermos alta latência em um Hadoop Distributed File System (HDFS) NameNode, queremos verificar a latência observada em cada aplicativo do Spark para garantir que esses problemas não tenham sido replicados.

Como os códigos de cliente NameNode estão incorporados em nossa biblioteca Spark, é complicado modificar seu código-fonte para adicionar essa métrica específica. Para acompanhar o crescimento perpétuo de nossa infraestrutura de dados, precisamos ser capazes de fazer as medições de qualquer aplicativo a qualquer momento e sem fazer alterações no código. Além disso, a implementação de um processo de coleta de métricas mais não-intrusivo nos permitiria injetar código dinamicamente nos métodos Java durante o tempo de carregamento.

Apresentando o JVM Profiler

Para resolver esses dois desafios, criamos e abrimos o código do nosso JVM Profiler. Existem algumas ferramentas open source existentes, como statsd-jvm-profiler da Etsy, que podem coletar métricas no nível de aplicativo individual, mas não fornecem a capacidade de injetar código dinamicamente no binário Java existente para coletar métricas. Inspirados por algumas dessas ferramentas, construímos nosso profiler com ainda mais recursos, como o perfil arbitrário de método/argumento Java.

O que o JVM Profiler faz?

O JVM Profiler é composto de três recursos principais que facilitam a coleta de métricas de desempenho e de uso de recursos e veiculam essas métricas (por exemplo, Apache Kafka) em outros sistemas para análise posterior:

  • Um agente Java: Ao incorporar um agente Java em nosso profiler, os usuários podem coletar várias métricas (por exemplo, uso da CPU/memória) e rastreamentos de pilha para processos da JVM de maneira distribuída.
  • Recursos avançados de criação de perfis: Nosso JVM Profiler nos permite rastrear métodos e argumentos Java arbitrários no código do usuário sem fazer nenhuma alteração real no código. Esse recurso pode ser usado para rastrear a latência de chamadas HDFS NameNode RPC para aplicativos Spark e identificar chamadas de método lentas. Ele também pode rastrear os caminhos de arquivo HDFS que cada aplicativo do Spark lê ou grava para identificar os arquivos ativos para otimização posterior.
  • Relatório de análise de dados: Na Uber, usamos o profiler para relatar métricas para tópicos do Kafka e tabelas do Apache Hive, tornando a análise de dados mais rápida e fácil.

Casos de uso típicos

Nosso JVM Profiler suporta uma variedade de casos de uso, principalmente possibilitando instrumentar código Java arbitrário. Usando uma mudança de configuração simples, o JVM Profiler pode se conectar a cada executor em um aplicativo Spark e coletar métricas de tempo de execução do método Java. Abaixo, abordamos alguns desses casos de uso:

  • Executor de tamanho à direita: Usamos métricas de memória do JVM Profiler para rastrear o uso de memória real para cada executor, para que possamos definir o valor apropriado para o argumento “executor-memory” do Spark.
  • Monitorar a latência de HDFS NameNode RPC: Nós fazemos o perfil dos métodos na classe org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB em um aplicativo Spark e identificamos latências longas em chamadas NameNode. Monitoramos mais de 50 mil aplicativos Spark a cada dia com vários bilhões dessas chamadas RPC.
  • Monitorar eventos descartados do driver: Fazemos perfil de métodos como org.apache.spark.scheduler.LiveListenerBus.onDropEvent para rastrear situações durante as quais a fila de eventos do driver Spark se torna muito longa e elimina eventos.
  • Rastrear linhagem de dados: Nós fazemos perfil de argumentos do caminho do arquivo no método org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getBlockLocations e org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.addBlock para rastrear quais arquivos são lidos e gravados pelo aplicativo Spark.

Detalhes de implementação e extensibilidade

Para tornar a implementação o mais simples possível, o JVM Profiler possui um design muito simples e extensível. As pessoas podem facilmente adicionar outras implementações de profiler para coletar mais métricas e também implementar seus próprios repórteres personalizados para enviar métricas para diferentes sistemas para análise de dados.

Figura 2. Nosso JVM Profiler é composto por vários perfis diferentes que medem métricas específicas relacionadas ao uso e desempenho da JVM.

O código do JVM Profiler é carregado em um processo Java por meio de um argumento do agente Java quando o processo é iniciado. Ele consiste em três partes principais:

  • Transformador de arquivos de classe: insere o bytecode do método Java dentro do processo para criar um perfil de código de usuário arbitrário e salvar métricas em um buffer métrico interno.
  • Perfis métricos
    • CPU/profiler de memória: coleta métricas de uso de CPU/memória via JMX e as envia para os repórteres.
    • Profiler de duração do método: lê as métricas de duração (latência) do método a partir do buffer de métricas e envia para os repórteres.
    • Profiler de argumento de método: lê valores de argumentos de métodos a partir do buffer de métricas e envia para os repórteres.
  • Repórteres
    • Repórter do console: grava métricas na saída do console.
    • Repórter Kafka: envia métricas para os tópicos do Kafka.

Como estender o JVM Profiler para enviar métricas via repórter customizado

Os usuários podem implementar seus próprios repórteres e especificá-los com a opção -javaagent, como:

java

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

Integração com a infraestrutura de dados da Uber

Figura 3. Nosso JVM Profiler se integra ao sistema de infraestrutura de dados da Uber.

Integramos nossas métricas do JVM Profiler à infraestrutura de dados interna da Uber para permitir:

  1. Análise de dados em todo o cluster: as métricas são primeiramente alimentadas no Kafka e ingeridas no HDFS, e os usuários consultam com Hive/Presto/Spark.
  2. Depuração do aplicativo Spark em tempo real: usamos o Flink para agregar dados para um único aplicativo em tempo real e gravar em nosso banco de dados MySQL, então os usuários podem visualizar as métricas por meio de uma interface baseada na web.

Usando o JVM Profiler

Abaixo, fornecemos instruções sobre como usar nosso JVM Profiler para rastrear um aplicativo Java simples.

Primeiro, vamos clonar o projeto:

git clone https://github.com/uber-common/jvm-profiler.git

Então, nós construímos o projeto rodando o seguinte comando:

mvn clean package

Em seguida, chamamos o arquivo JAR de resultado de construção (por exemplo: targetget/jvm-profiler-0.0.5.jar) e rodamos o aplicativo dentro do JVM Profiler usando o seguinte comando:

java -javaagent:target/jvm-profiler-0.0.5.jar=reporter=com.uber.profiling.reporters.ConsoleOutputReporter -cp target/jvm-profiler-0.0.5.jar com.uber.profiling.examples.HelloWorldApplication

O comando executa o aplicativo Java de amostra e relata suas métricas de desempenho e uso de recursos para o console de saída. Por exemplo:

O profiler também pode enviar métricas para um tópico do Kafka por meio de um comando como o seguinte:

Use o profiler para criar o perfil do aplicativo Spark

Agora, vamos explicar como executar o profiler com o aplicativo Spark.

Supondo que já tenhamos um cluster HDFS, carregamos o arquivo JAR do JVM Profiler em nosso HDFS:

hdfs dfs -put target/jvm-profiler-0.0.5.jar hdfs://hdfs_url/lib/jvm-profiler-0.0.5.jar

Em seguida, usamos a linha de comando spark-submit para iniciar o aplicativo Spark com o profiler:

spark-submit --deploy-mode cluster --master yarn --conf spark.jars=hdfs://hdfs_url/lib/jvm-profiler-0.0.5.jar --conf spark.driver.extraJavaOptions=-javaagent:jvm-profiler-0.0.5.jar --conf spark.executor.extraJavaOptions=-javaagent:jvm-profiler-0.0.5.jar --class com.company.SparkJob spark_job.jar

Exemplos de consulta métrica

Na Uber, enviamos nossas métricas para os tópicos do Kafka e programamos os pipelines de dados de segundo plano para inseri-los automaticamente nas tabelas do Hive. Os usuários podem configurar pipelines semelhantes e usar o SQL para consultar as métricas do profiler. Eles também podem escrever seus próprios repórteres para enviar as métricas para um banco de dados SQL como o MySQL e consultar a partir daí. Abaixo está um exemplo de um esquema de tabela Hive.

Abaixo, oferecemos um exemplo de resultado ao executar uma consulta SQL anterior, que mostra as métricas de memória e CPU para cada processo dos executores do Spark:

Resultados e próximos passos

Aplicamos o JVM Profiler a um dos maiores aplicativos Spark da Uber (que usa mais de 1.000 executores) e, no processo, reduzimos a alocação de memória para cada executor em 2 GB, passando de 7 GB para 5 GB. Somente para esse aplicativo Spark, economizamos 2 TB de memória.

Também aplicamos o JVM Profiler em todos os aplicativos Hive no Spark dentro da Uber e encontramos algumas oportunidades para melhorar a eficiência do uso da memória. A Figura 3, abaixo, mostra um resultado que encontramos: cerca de 70% de nossos aplicativos usaram menos de 80% de sua memória alocada. Nossas descobertas indicaram que poderíamos alocar menos memória para a maioria desses aplicativos e aumentar a utilização da memória em 20%.

Figura 3. Nosso JVM Profiler identificou que 70% dos aplicativos estavam usando menos de 80% de sua memória alocada.

À medida que continuamos a aumentar nossa solução, esperamos uma redução adicional de memória em nossas JVMs.

O JVM Profiler é um projeto open source independente e nós damos boas-vindas a outros desenvolvedores para usar essa ferramenta e colaborar (por exemplo, enviar pull requests) também!

Nossa equipe de Big Data Engineering em Seattle, Palo Alto e San Francisco está trabalhando em ferramentas e sistemas para expandir todo o ecossistema Hadoop, incluindo HDFS, Hive, Presto e Spark. Criamos tecnologias em cima dessa família open source para nos ajudar a tomar decisões de negócios melhores e baseadas em dados. Se isso soa atraente para você, confira nossas oportunidades de emprego e considere fazer parte de nossa equipe!

***

Este artigo é do Uber Engineering. Ele foi escrito por Bo Yang, Nan Zhu, Felix Cheung e Xu Ning. A tradução foi feita pela Redação iMasters com autorização. Você pode conferir o original em: https://eng.uber.com/jvm-profiler/