Desmistificando ByteDance Cloud Native Spark History Service UIService

Este artigo é o compartilhamento de prática de otimização do Spark History Server (SHS) pela equipe SparkSQL, o mecanismo de dados da plataforma de dados ByteDance.

Text | ByteDance Data Platform - Data Engine - Equipe SparkSQL

Dentro do ByteDance, implementamos um novo conjunto de serviço Spark History nativo da nuvem - UIService. Em comparação com o SHS de código aberto, a ocupação de armazenamento e a latência de acesso do UIService são reduzidas em mais de 90%. Atualmente, o UIService tem sido amplamente utilizado no ByteDance. É usado como o serviço padrão do LAS (LakeHouse Analytics Service).

fundo de negócios

Arquitetura de servidor de histórico do Spark de código aberto

Para entender melhor o histórico e o significado dessa refatoração, vamos primeiro apresentar brevemente o princípio do Spark History Server nativo.

Fluxograma do Spark History Server de código aberto

O histórico do Spark é construído no sistema Spark Event. Durante a execução de tarefas do Spark, um grande número de SparkListenerEvents contendo informações de execução será gerado, como ApplicationStart / StageCompleted / MetricsUpdate, etc., todos com implementações de SparkListenerEvent correspondentes. Todos os eventos serão enviados ao ListenerBus e monitorados por todos os ouvintes cadastrados no ListenerBus. Entre eles, EventLoggingListener é um listener especialmente usado para gerar log de eventos. Ele serializará o evento em um arquivo de log de eventos formatado em Json e o gravará no sistema de arquivos (como HDFS). Normalmente, os arquivos das tarefas de uma sala de informática são armazenados em um caminho.

No lado do History Server, a lógica principal está em FsHistoryProvider. O FsHistoryProvider manterá um encadeamento para verificar o caminho de armazenamento do log de eventos configurado de forma intermitente, percorrer os arquivos de log de eventos, extrair informações de resumo (principalmente application_id, usuário, status, start_time, end_time, event_log_path) e manter uma lista. Quando o usuário acessar a interface do usuário, a tarefa necessária para a solicitação será pesquisada na lista e, se existir, o arquivo de log de eventos correspondente será completamente lido e analisado. O processo de análise é um processo de repetição. Cada linha no arquivo de log de eventos é um evento serializado, desserialize-os linha por linha e use ReplayListener para retornar as informações ao KVStore para restaurar o estado da tarefa.

Independentemente do tempo de execução ou do History Server, os estados das tarefas são armazenados em um número limitado de instâncias de classes e são armazenados no KVStore, um armazenamento KV baseado em memória no Spark que pode armazenar instâncias de classe arbitrárias. O front end consultará os objetos necessários do KVStore para renderizar a página.

Pontos de dor

O espaço de armazenamento é caro

O sistema de eventos do Spark é muito detalhado, resultando em um número muito grande de eventos registrados no log de eventos.Para exibição na interface do usuário, a maioria dos eventos é inútil. E o log de eventos geralmente é armazenado em texto simples json, o que ocupa muito espaço. Para tarefas mais complexas ou longas, o log de eventos pode atingir dezenas de GB. O log de eventos de 7 dias em bytes ocupa cerca de 3,2 PB de espaço de armazenamento HDFS.

Baixa eficiência de reprodução e alta latência

O History Server restaura a interface do usuário do Spark reproduzindo e analisando o log de eventos, que tem muita sobrecarga computacional. Quando a tarefa é grande, haverá um atraso de resposta significativo. O atraso de resposta refere-se ao tempo de espera de quando o usuário inicia um o acesso de front-end à interface do usuário da página é totalmente renderizado. Após o término do trabalho, o usuário pode ter que esperar dez minutos ou até meia hora para ver o histórico do trabalho por meio do History Server. Depois que um trabalho grande é concluído, os usuários geralmente desejam ver o histórico de trabalhos o mais rápido possível para que possam diagnosticar problemas e otimizar trabalhos com base no histórico de trabalhos. Demora muito para os usuários aguardarem a conclusão da renderização da interface do usuário, o que afeta muito a experiência do usuário.

Escalabilidade ruim

Conforme mencionado acima, o FsHistoryProvider do History Server precisa verificar o caminho do log de eventos configurado, percorrer o log de eventos e carregar as informações meta de todos os arquivos na memória antes de reproduzir o arquivo analisado, o que torna o serviço nativo um serviço com estado. Portanto, toda vez que o serviço é reiniciado, todo o caminho precisa ser recarregado antes que possa ser servido externamente. Após a conclusão de cada tarefa, ela também precisa aguardar a próxima rodada de verificação antes de poder ser acessada.

Quando o número de tarefas de cluster aumenta, o consumo de tempo de cada rodada de varredura de arquivos e o uso de memória de metainformações aumentarão, o que também exige que o serviço tenha uma alocação de recursos cada vez maior. Se a pressão de uma única instância for reduzida pela divisão do caminho do log de eventos, as regras de roteamento precisam ser modificadas, o que aumenta a dificuldade de operação e manutenção. Atualmente, ByteDance pode facilmente expandir horizontalmente adicionando instâncias UIService.

Não nativo da nuvem

O Spark History Server não é um serviço nativo da nuvem e o custo de transformação e manutenção é alto em cenários de nuvem pública. Primeiro, o cenário de nuvem pública requer isolamento de recursos de locatário. Em segundo lugar, no cenário de nuvem pública, as cargas de trabalho de diferentes usuários variam muito e o volume de tarefas de diferentes usuários varia em ordens de magnitude, resultando em um grande número de trabalhos de cauda longa . A implantação do History Server separadamente para cada usuário requer custos excessivos de computação e armazenamento, e a implantação de um History Server unificado não consegue isolar os recursos.Quando ocorrer um problema que afete muitos usuários, os custos de operação e manutenção dos dois métodos serão altos. O LAS (Lakehouse Analytics Service), um mecanismo de análise integrado do mecanismo do vulcão e da casa do lago, fornece um UIService nativo da nuvem, que pode resolver efetivamente os problemas acima.

Serviço de interface do usuário

Programa

Para resolver os três problemas anteriores, tentamos transformar o History Server. Como mencionado acima, independentemente de estar executando o Spark Driver ou o History Server, ao monitorar o evento, as informações de alteração de tarefa contidas nele são refletidas nas instâncias de várias classes relacionadas à interface do usuário e, em seguida, armazenadas no KVStore para renderização da interface do usuário. Ou seja, o KVStore armazena as informações completas necessárias para a exibição da interface do usuário. Para usuários do History Server, na maioria dos casos, nos preocupamos apenas com o estado final da tarefa e não precisamos nos preocupar com os eventos específicos que causam alterações de estado. Portanto, só podemos persistir KVStore sem armazenar muitas informações de eventos redundantes. Além disso, o KVStore oferece suporte nativo à serialização Kryo e seu desempenho é significativamente melhor do que a serialização Json. Reescrevemos um novo sistema History Server baseado nessa ideia, chamado UIService. Diagrama de quadro UIService

concluir

UIMetaStore Todas as instâncias de classe relacionadas à interface do usuário no KVStore são chamadas coletivamente de classes UIMeta. Isso inclui informações de AppStatusStore e SQLAppStatusStore (listadas abaixo). Definimos uma classe UIMetaStore para abstrair, uma UIMetaStore é uma coleção de todas as informações da interface do usuário para uma tarefa. Informações contidas na UIMetaStore:

#AppStatusStore org.apache.spark.status.JobDataWrapper org.apache.spark.status.ExecutorStageSummaryWrapper org.apache.spark.status.ApplicationInfoWrapper org.apache.spark.status.PoolData org.apache.spark.status.ExecutorSummaryWrapper org.apache.spark.status.StageDataWrapper org.apache.spark.status.AppSummary org.apache.spark.status.RDDOperationGraphWrapper org.apache.spark.status.TaskDataWrapper org.apache.spark.status.ApplicationEnvironmentInfoWrapper #SQLAppStatusStore org.apache.spark.sql.execution.ui.SQLExecutionUIData org.apache.spark.sql.execution.ui.SparkPlanGraphWrapper

UIMetaStore também define a estrutura de dados de arquivos persistentes, a estrutura é a seguinte:

4-Byte Magic Number: "UI_S" ----------- Body --------------- 4_byte_length_of_class_name | class_name_str1 | 4_byte_length | serialized_of_class1_instance1 4_byte_length_of_class_name | class_name_str1 | 4_byte_length | serialized_of_class1_instance2 4_byte_length_of_class_name | class_name_str2 | 4_byte_length | serialized_of_class2_instance1 4_byte_length_of_class_name | class_name_str2 | 4_byte_length | serialized_of_class2_instance2

  • Magic Number é usado para verificação de identificação de tipo de arquivo.
  • Body são os dados do corpo do UIMetaStore, usando armazenamento contínuo. Cada instância de classe relacionada à interface do usuário é serializada em quatro segmentos: comprimento do nome da classe (tipo de 4 bytes) + nome da classe (tipo de string) + comprimento dos dados (tipo de 4 bytes) + dados serializados (tipo binário). Lido sequencialmente durante a leitura, cada elemento primeiro lê as informações de comprimento e, em seguida, lê os dados correspondentes subsequentes de acordo com o comprimento para desserialização.
  • A serialização usando o KVStoreSerializer nativo do Spark garante compatibilidade com versões anteriores e posteriores.

UIMetaLoggingListener

Semelhante ao EventLoggingListener, um Listener dedicado - UIMetaLoggingListener é desenvolvido para UIMeta, que é usado para monitorar eventos e gravar arquivos UIMeta. Contraste com EventLoggingListener: EventLoggingListener acionará a gravação toda vez que aceitar um evento, que é um evento serializado; enquanto UIMetaLoggingListener será acionado apenas por um evento específico, atualmente ele será acionado apenas por eventos stageEnd e JobEnd, mas cada operação de gravação é em lote Write, as informações do UIMetaStore na etapa anterior são totalmente persistidas. Para fazer uma analogia, EventLoggingListener é como um fluxo, que acrescenta gravações continuamente, enquanto UIMetaLoggingListener é como um tipo de lote, que periodicamente captura o status da tarefa.

UIMetaProviderName

Substitua o FsHistoryProvider original, as principais diferenças são:

Altere o processo de leitura de arquivos de log de eventos e reprodução para gerar KVStore para leitura de UIMetaFile e desserialização de UIMetaStore.

Removida a lógica de varredura de caminho do FsHistoryProvider; cada acesso à interface do usuário, de acordo com as regras appid e de caminho, lê diretamente a análise UIMetaFile. Isso faz com que o UIService não precise pré-carregar todas as metainformações do arquivo e não precise aumentar a configuração do servidor à medida que o número de tarefas aumenta, o que facilita a expansão horizontal.

otimização

Evite repetição

Como a conclusão de cada etapa acionará a escrita do arquivo UIMeta, para muitos elementos da UIMeta, pode ocorrer persistência repetida, aumentando o tempo de escrita e o tamanho do arquivo. Portanto, mantemos um mapa dentro do UIMetaLoggingListener para registrar as instâncias que foram serializadas. Filtre ao gravar arquivos UIMeta, apenas grave elementos que não foram gravados ou cujos dados foram alterados. Isso pode eliminar a maior parte da redundância de gravação.

Além disso, durante o desenvolvimento, verificou-se que as informações de nível de tarefa TaskDataWrapper ocupam o maior espaço. Quando um estágio termina de acionar uma gravação. A tarefa do estágio que ainda está no estado RUNNING pode ser serializada, de modo que quando o estágio de RUNNING for concluído, as informações da tarefa serão escritas novamente, o que também causará redundância de dados. no estágio No final, apenas as informações da tarefa cujo estado é Concluído são mantidas.

Suporte de fallback para log de eventos

Tendo em vista o risco de problemas com UIService no estágio inicial, também suportamos um mecanismo de fallback, ou seja, para acessar a UI de uma tarefa, primeiro tente pegar o caminho de UIService: analise o arquivo UIMeta, se o arquivo UIMeta não existir ou a análise relatar um erro, ele retornará à leitura O caminho do arquivo de log de eventos para evitar falha de acesso à interface do usuário. Ele também suporta a conversão de arquivos de log de eventos em arquivos UIMeta, para que UIService possa ser usado na próxima chamada. Esse recurso garante um processo de migração tranquilo para nós.

renda

Renda de armazenamento

Testes online mostram uma redução média de 85% no armazenamento e uma redução de 92,4% no total. A figura a seguir mostra o monitoramento da ocupação de armazenamento do log de eventos e UIMeta em uma sala de informática.Pode-se observar que o UIMeta tem uma redução de ordem de magnitude na capacidade de armazenamento em comparação com o log de eventos. Atualmente, o log de eventos de 7 dias em Byte ocupa 3,2 PB de espaço de armazenamento.Após mudar para UIMeta, o espaço ocupado é de apenas 350TB. Com as vantagens de armazenamento do UIService, podemos reter as informações de log por mais tempo, o que é útil para análise histórica e recuperação de problemas. Agora aumentamos a retenção de logs de 7 dias para 30 dias e podemos aumentar o tempo de retenção conforme necessário. Comparação do log de eventos/monitoramento de armazenamento UIMeta HDFS em uma sala de informática

Benefício de atraso de acesso

Atraso de acesso: redução de 35% em média, redução de 84,6%/90,8%/93,7% em PCT90/95/99 respectivamente . A distribuição percentual de atraso de acesso é mostrada na figura abaixo. esquerda em comparação com o log de eventos, com uma cauda longa. As tarefas são significativamente reduzidas. Gráfico de distribuição de latência de acesso

Benefícios da arquitetura

O caminho de travessia original do History Server e o link demorado de pré-carregamento são removidos e o intervalo de tempo desde a conclusão da tarefa até a acessibilidade do History Server é reduzido da média original de cerca de 10 minutos para o segundo nível, e o serviço pode ser prestado ao mundo exterior imediatamente após a conclusão da tarefa. Ao mesmo tempo, o History Server pode ser estendido horizontalmente, o que pode lidar melhor com os desafios trazidos pelo aumento da quantidade de tarefas no futuro.

Atualmente, podemos facilmente dimensionar horizontalmente adicionando instâncias UIService dentro do ByteDance. No serviço de análise integrado LAS do mecanismo do vulcão, lago e armazém, também implementamos um Spark History Server escalável e nativo da nuvem baseado em UIService que oferece suporte ao isolamento de acesso de locatário.

Clique para conhecer o serviço de análise integrada de lago de motor vulcânico e armazém LAS

Bem-vindo à conta pública com o mesmo nome da plataforma de dados ByteDance

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/5588928/blog/5486792