JVM Profiler: Uma ferramenta de código aberto para rastrear aplicações JVM distribuídas em escala | Uber Engineering Blog

JVM Profiler: Uma ferramenta de código aberto para rastrear aplicações JVM distribuídas em escala
954 Ações

Aplicações de computação como Apache Spark têm sido amplamente adotadas para construir aplicações de dados em larga escala. Para Uber, os dados estão no centro da tomada de decisões estratégicas e do desenvolvimento de produtos. Para nos ajudar a melhor aproveitar esses dados, nós gerenciamos implantações massivas da Spark em nossos escritórios de engenharia globais.

Embora a Spark torne a tecnologia de dados mais acessível, dimensionando corretamente os recursos alocados para aplicações Spark e otimizando a eficiência operacional de nossa infra-estrutura de dados requer percepções mais refinadas sobre estes sistemas, ou seja, seus padrões de uso de recursos.

Para minerar estes padrões sem alterar o código do usuário, nós construímos e abrimos o JVM Profiler, um profiler distribuído para coletar métricas de desempenho e uso de recursos e servi-los para análises adicionais. Embora construído para Spark, sua implementação genérica o torna aplicável a qualquer máquina virtual Java (JVM), serviço ou aplicação. Leia mais para saber como Uber usa esta ferramenta para traçar o perfil das nossas aplicações Spark, bem como como usá-la para os seus próprios sistemas.

Desafios de perfilamento

Em uma base diária, Uber suporta dezenas de milhares de aplicações rodando através de milhares de máquinas. Com o crescimento da nossa pilha de tecnologia, percebemos rapidamente que a nossa solução de perfil de desempenho e otimização existente não seria capaz de atender às nossas necessidades. Em particular, nós queríamos a capacidade de:

Correlacionar métricas através de um grande número de processos ao nível da aplicação

Em um ambiente distribuído, múltiplas aplicações Spark rodam no mesmo servidor e cada aplicação Spark tem um grande número de processos (por exemplo, milhares de executores) rodando através de muitos servidores, como ilustrado na Figura 1:

Figure 1. Em um ambiente distribuído, as aplicações Spark rodam em múltiplos servidores.

Nossas ferramentas existentes só podiam monitorar métricas a nível de servidor e não mediam métricas para aplicações individuais. Nós precisávamos de uma solução que pudesse coletar métricas para cada processo e correlacioná-las entre processos para cada aplicação. Além disso, nós não sabemos quando esses processos serão iniciados e quanto tempo eles levarão. Para ser capaz de coletar métricas neste ambiente, o profiler precisa ser lançado automaticamente com cada processo.

Faça a coleta de métricas não intrusiva 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 performance; no entanto, há frequentemente situações em que precisamos coletar essas métricas sem alterar o código do usuário ou do framework. Por exemplo, se experimentarmos alta latência em um Hadoop Distributed File System (HDFS) NameNode, queremos verificar a latência observada em cada aplicação Spark para garantir que esses problemas não foram replicados. Como os códigos dos clientes NameNode estão embutidos dentro da nossa biblioteca Spark, é incômodo modificar seu código fonte para adicionar essa métrica específica. Para acompanhar o crescimento perpétuo da nossa infra-estrutura de dados, precisamos ser capazes de tomar as medidas de qualquer aplicação a qualquer momento e sem fazer alterações no código. Além disso, implementar um processo de coleta de métricas mais não intrusivas nos permitiria injetar código dinamicamente nos métodos Java durante o tempo de carga.

Introducing JVM Profiler

Para enfrentar estes dois desafios, nós construímos e abrimos o nosso JVM Profiler. Existem algumas ferramentas de código aberto existentes, como o statsd-jvm-profiler do Etsy, que poderiam coletar métricas em nível de aplicação individual, mas não fornecem a capacidade de injetar código dinamicamente no binário Java existente para coletar métricas. Inspirado por algumas dessas ferramentas, construímos o nosso profiler com ainda mais capacidades, tais como o método arbitrário Java/argument profiling.

O que faz o JVM Profiler?

O JVM Profiler é composto por três características chave que facilitam a coleta de métricas de performance e uso de recursos, e então servir essas métricas (por exemplo, Apache Kafka) para 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 de CPU/memória) e empilhar traços para processos JVM de uma forma distribuída.
  • Capacidades avançadas de criação de perfil: 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. Este recurso pode ser usado para rastrear a latência de chamadas HDFS NameNode RPC para aplicações Spark e identificar chamadas de métodos lentos. Ele também pode rastrear os caminhos de arquivo HDFS que cada aplicação Spark lê ou escreve para identificar arquivos quentes para maior otimização.
  • Relatórios analíticos de dados: No Uber, usamos o profiler para relatar métricas para tópicos Kafka e tabelas Apache Hive, tornando a análise de dados mais rápida e fácil.

Casos de uso típico

O nosso JVM Profiler suporta uma variedade de casos de uso, tornando mais notável a possibilidade de instrumentar código Java arbitrário. Usando uma simples mudança de configuração, o JVM Profiler pode anexar a cada executor em uma aplicação Spark e coletar métricas de tempo de execução do método Java. Abaixo, tocamos em alguns destes casos de uso:

  • Executor do tamanho certo: Nós usamos métricas de memória do JVM Profiler para rastrear o uso real de memória para cada executor para que possamos definir o valor adequado para o argumento Spark “executor-memória”.
  • Monitorar a latência do HDFS NameNode RPC: Nós perfilamos métodos na classe org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB em uma aplicação Spark e identificamos latências longas em chamadas NameNode. Monitoramos mais de 50 mil aplicações Spark a cada dia com vários bilhões de chamadas RPC.
  • Monitorar eventos de queda de drivers: Nós perfilamos 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 deixa cair os eventos.
  • Rastrear a linhagem de dados: Nós perfilamos argumentos de caminho de 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 escritos pela aplicação Spark.

Detalhes de implementação e extensibilidade

Para tornar a implementação o mais simples possível, o JVM Profiler tem um design muito simples e extensível. As pessoas podem facilmente adicionar implementações adicionais do 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.

Figure 2. Nosso JVM Profiler é composto de vários profilers diferentes que medem métricas específicas relacionadas ao uso e desempenho do JVM.

O código do JVM Profiler é carregado em um processo Java através de um argumento de agente Java uma vez que o processo é iniciado. Ele consiste de três partes principais:

  • Class File Transformer: instrumentos do método Java bytecode dentro do processo para fazer o perfil do código arbitrário do usuário e salvar métricas em um buffer de métricas interno.
  • Metric Profilers
    • CPU/Memory Profiler: coleta métricas de uso de CPU/Memory via JMX e as envia para os repórteres.
    • Método Duration Profiler: lê as métricas de duração do método (latência) do buffer de métricas e as envia para os repórteres.
    • Método Argument Profiler: lê os valores dos argumentos do método a partir do buffer de métricas e os envia para os repórteres.
  • Repórteres
    • Repórter de Console: escreve métricas na saída do console.
    • Repórter Kafka: envia métricas para os tópicos Kafka.

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

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 infra-estrutura de dados de Uber

Figure 3. Nosso JVM Profiler integra-se com o sistema de infra-estrutura de dados de Uber.

Integração das métricas do nosso JVM Profiler com a infra-estrutura de dados interna de Uber para permitir:

  1. Análise de dados em todo o cluster: As métricas são primeiro alimentadas para Kafka e ingeridas para HDFS, depois os usuários consultam com Hive/Presto/Spark.
  2. Depuração da aplicação Spark em tempo real: Nós usamos Flink para agregar dados para uma única aplicação em tempo real e escrever no nosso banco de dados MySQL, então os usuários podem ver as métricas através de uma interface baseada na web.

Usando o JVM Profiler

Below, nós fornecemos instruções de como usar o nosso JVM Profiler para rastrear uma aplicação Java simples:

Primeiro, nós clonamos o projeto:

A compilação do projeto executando o seguinte comando:

Próximo, chamamos o resultado da compilação de arquivo JAR (por exemplo.target/jvm-profiler-0.0.5.jar) e executamos a aplicação dentro do JVM Profiler usando o seguinte comando:

O comando executa a aplicação Java de exemplo e relata o seu desempenho e métricas de uso de recursos para o console de saída. Por exemplo:

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

Utiliza o profiler para perfilar a aplicação Spark

Agora, vamos caminhar através de como executar o profiler com a aplicação Spark.

Assumindo que já temos um cluster HDFS, carregamos o ficheiro JAR JVM Profiler para o nosso HDFS:

Em seguida usamos a linha de comandos spark-submit para iniciar a aplicação Spark com o profiler:

Exemplos de consulta métrica

Em Uber, enviamos as nossas métricas para tópicos Kafka e pipelines de dados de fundo do programa para os ingerir automaticamente nas tabelas Hive. Os utilizadores podem configurar pipelines semelhantes e utilizar 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:

Below, nós oferecemos um resultado de exemplo quando se executa uma consulta SQL anterior, que mostra as métricas de memória e CPU para cada processo para os executores Spark:

role processUuid maxHeapMemoryMB avgProcessCpuLoad
executor 6d3aaa4ee-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

Resultados e próximos passos

Aplicamos o JVM Profiler a uma das maiores aplicações do Uber Spark (que usa mais de 1.000 executores), e no processo, reduzimos a alocação de memória para cada executor em 2GB, passando de 7GB para 5GB. Somente para esta aplicação Spark, salvamos 2TB de memória.

Aplicamos também o JVM Profiler em todas as aplicações Spark dentro do Uber, e encontramos algumas oportunidades para melhorar a eficiência na utilização da memória. A figura 3, abaixo, mostra um resultado que encontrámos: cerca de 70% das nossas aplicações utilizaram menos de 80% da memória que lhes foi atribuída. Os nossos resultados indicam que poderíamos alocar menos memória para a maioria destas aplicações e aumentar a utilização da memória em 20%.

Figure 3. Nosso JVM Profiler identificou que 70% das aplicações estavam usando menos de 80% de sua memória alocada.

Como nós continuamos a crescer nossa solução, nós esperamos por uma redução adicional de memória através de nossas JVMs.

JVM Profiler é um projeto auto-contido de código aberto e damos as boas vindas a outros desenvolvedores para usar esta ferramenta e contribuir (por exemplo, enviar pedidos de puxar) também!

Nossa equipe de Engenharia de Grandes Dados em Seattle, Palo Alto e São Francisco está trabalhando em ferramentas e sistemas para expandir todo o ecossistema Hadoop, incluindo HDFS, Hive, Presto e Spark. Construímos tecnologias em cima desta família de software de código aberto para nos ajudar a tomar melhores decisões comerciais, orientadas por dados. Se isto lhe parece apelativo, verifique as nossas oportunidades de trabalho e considere juntar-se à nossa equipa!

Cabeçalho de Crédito Fotográfico: Seahorse, Roatan, Honduras por Conor Myhrvold.

Subscreva a nossa newsletter para se manter actualizado com as últimas inovações de Uber Engineering.

Deixe uma resposta

O seu endereço de email não será publicado.