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
- Correlacionar métricas através de um grande número de processos ao nível da aplicação
- Faça a coleta de métricas não intrusiva para código de usuário arbitrário
- Introducing JVM Profiler
- O que faz o JVM Profiler?
- Casos de uso típico
- Detalhes de implementação e extensibilidade
- Como estender o JVM Profiler para enviar métricas via um repórter personalizado
- Integração com a infra-estrutura de dados de Uber
- Usando o JVM Profiler
- Utiliza o profiler para perfilar a aplicação Spark
- Exemplos de consulta métrica
- Resultados e próximos passos
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:
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.
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
Integração das métricas do nosso JVM Profiler com a infra-estrutura de dados interna de Uber para permitir:
- 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.
- 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%.
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.