Olá, Habr! Ontem, no , tivemos muitas perguntas dos participantes relacionadas à configuração dessa ferramenta, feitas pelos pessoal da Rambler&Co. Decidimos compartilhar nossa experiência a partir dessas discussões. O assunto não é fácil — portanto, convidamos todos a também compartilharem suas experiências nos comentários, talvez encontremos algo que não entendemos ou usamos corretamente.
Uma breve introdução — como utilizamos o Spark. Temos um programa de três meses , e durante o segundo módulo nossos participantes trabalham com essa ferramenta. Portanto, nossa tarefa, como organizadores, é preparar um cluster para uso nesse contexto.
A peculiaridade do nosso uso é que o número de pessoas que trabalham simultaneamente no Spark pode ser igual ao total do grupo. Por exemplo, em um seminário, quando todos estão tentando algo ao mesmo tempo e copiando o que nosso instrutor faz. Isso pode chegar a quase 40 pessoas, por vezes. Provavelmente, não há muitas empresas no mundo que enfrentam esse cenário de uso.
A seguir, vou explicar como e por que escolhemos determinados parâmetros de configuração.
Comecemos pelo começo. O Spark tem 3 opções de operação em um cluster: standalone, usando Mesos e usando YARN. Decidimos escolher a terceira opção, pois parecia lógica para nós. Já temos um cluster Hadoop. Nossos participantes já estão bem familiarizados com sua arquitetura. Vamos usar o YARN.
spark.master=yarnAgora fica mais interessante. Cada uma dessas 3 opções de implantação tem 2 opções de implementação: client e cluster. Com base em e diferentes referências na internet, pode-se concluir que o modo client é adequado para trabalho interativo — por exemplo, através do jupyter notebook, enquanto o modo cluster é mais adequado para soluções em produção. No nosso caso, estávamos interessados no trabalho interativo, portanto:
spark.deploy-mode=clientNa verdade, a partir deste momento, o Spark já funcionará de alguma forma no YARN, mas isso não era suficiente para nós. Como temos um programa sobre grandes dados, às vezes os participantes sentiam falta do que era possível com um corte uniforme de recursos. E aqui encontramos algo interessante - a alocação dinâmica de recursos. Resumidamente, a ideia é a seguinte: se você tem uma tarefa pesada e o cluster está livre (por exemplo, de manhã), então, com essa opção, o Spark pode te fornecer recursos adicionais. A necessidade é calculada por uma fórmula astuta. Não vamos entrar em detalhes - ela funciona bem.
spark.dynamicAllocation.enabled=trueColocamos esse parâmetro, e ao iniciar o Spark ele reclamou e não começou. Certo, porque era necessário ler com mais atenção. Lá está indicado que, para tudo estar certo, é necessário ativar um parâmetro adicional.
spark.shuffle.service.enabled=truePara que ele serve? Quando o nosso job não exige mais tanta quantidade de recursos, o Spark deve devolvê-los ao pool geral. A fase mais trabalhosa em quase qualquer tarefa MapReduce é a fase Shuffle. Este parâmetro permite armazenar os dados que são gerados nessa fase e, consequentemente, liberar os executores. E o executor é o processo que calcula tudo no worker. Ele tem um certo número de núcleos de processador e uma certa quantidade de memória.
Adicionamos esse parâmetro. Tudo parece ter funcionado. Ficou evidente que os participantes realmente estavam recebendo mais recursos quando precisavam. Mas surgiu outro problema - em determinado momento, outros participantes acordavam e também queriam usar o Spark, e tudo estava ocupado, e eles ficaram insatisfeitos. É compreensível. Começamos a olhar na documentação. Lá parecia que havia mais alguns parâmetros que podiam influenciar no processo. Por exemplo, se o executor está no modo de espera - após quanto tempo ele pode ter seus recursos retirados?
spark.dynamicAllocation.executorIdleTimeout=120sNo nosso caso, se seus executors não fizerem nada por dois minutos, por favor, devolva-os ao pool comum. Mas esse parâmetro nem sempre foi suficiente. Era evidente que a pessoa já não estava fazendo nada há muito tempo, mas os recursos não estavam sendo liberados. Descobrimos que havia um parâmetro especial - após quanto tempo os executors que contêm dados em cache devem ser retirados. Por padrão, esse parâmetro estava configurado como - infinito! Nós o corrigimos.
spark.dynamicAllocation.cachedExecutorIdleTimeout=600sOu seja, se em 5 minutos seus executors não fizerem nada, devolva-os ao pool comum. Nesse modo, a velocidade de liberação e entrega de recursos para um grande número de usuários tornou-se aceitável. O nível de insatisfação diminuiu. Mas decidimos ir mais longe e limitar o número máximo de executors para um único aplicativo - essencialmente para um único participante do programa.
spark.dynamicAllocation.maxExecutors=19Agora, claro, surgiram descontentes do outro lado - “o cluster está ocioso e eu tenho apenas 19 executors”, mas o que fazer - é preciso encontrar um equilíbrio adequado. Não é possível deixar todos felizes.
E mais uma pequena história, relacionada à especificidade do nosso caso. Certa vez, algumas pessoas se atrasaram para a aula prática, e o Spark não conseguiu iniciar por algum motivo. Olhamos para a quantidade de recursos livres - parecia haver suficientes. O Spark deveria iniciar. Felizmente, naquela época a documentação já havia se fixado na nossa memória, e lembramos que ao iniciar o Spark busca uma porta para iniciar. Se a primeira porta do intervalo estiver ocupada, ele passa para a próxima. Se esta estiver livre, ele a captura. E há um parâmetro que indica o número máximo de tentativas para isso. Por padrão, é 16. Um número menor do que o número de pessoas em nosso grupo na aula. Assim, após 16 tentativas, o Spark desistia e dizia que não conseguia iniciar. Corrigimos esse parâmetro.
spark.port.maxRetries=50A seguir, falarei sobre algumas configurações que já não estão tão relacionadas à especificidade do nosso caso.
Para um start mais rápido do Spark, há a recomendação de compactar a pasta jars, que fica no diretório inicial do SPARK_HOME, e colocá-la no HDFS. Assim, ele não perderá tempo carregando esses jars pelos workers.
spark.yarn.archive=hdfs:///tmp/spark-archive.zipAlém disso, para um desempenho mais rápido, recomenda-se usar o kryo como serializador. Ele é mais otimizado do que o padrão.
spark.serializer=org.apache.spark.serializer.KryoSerializerE há um problema antigo no Spark, que é a falta de memória. Isso costuma acontecer quando os trabalhadores calculam tudo e enviam o resultado para o driver. Nós aumentamos esse parâmetro. Por padrão, é 1GB, mas nós ajustamos para 3.
spark.driver.maxResultSize=3072Por último, como um adicional. Como atualizar o Spark para a versão 2.1 na distribuição HortonWorks — HDP 2.5.3.0. Esta versão do HDP contém uma versão pré-instalada 2.0, mas decidimos que o Spark está se desenvolvendo ativamente e cada nova versão corrige alguns bugs e oferece novas funcionalidades, incluindo para a API do Python, por isso decidimos realizar a atualização.
Baixamos a versão do site oficial para Hadoop 2.7. Descompactamos e colocamos na pasta do HDP. Criamos os links simbólicos adequadamente. Ao iniciar, não funciona. Aparece um erro muito confuso.
java.lang.NoClassDefFoundError: com/sun/jersey/api/client/config/ClientConfigPesquisando no Google, descobrimos que o Spark decidiu não esperar o Hadoop e optou por usar uma nova versão do jersey. Eles comentam sobre isso entre si no JIRA. A solução foi - baixar . Colocar isso na pasta jars em SPARK_HOME, compactar novamente e colocar no HDFS.
Contornamos esse erro, mas um novo problema surgiu e foi bastante vago.
org.apache.spark.SparkException: A aplicação Yarn já terminou! Ela pode ter sido encerrada ou não conseguiu iniciar o master da aplicação.Nesse meio tempo, tentamos iniciar a versão 2.0 — tudo ok. Tente adivinhar qual é o problema. Analisamos os logs dessa aplicação e vimos algo assim:
/usr/hdp/${hdp.version}/hadoop/lib/hadoop-lzo-0.6.0.${hdp.version}.jarDe qualquer forma, por algum motivo, hdp.version não foi resolvida. Pesquisando, encontramos a solução. É necessário entrar nas configurações do Ambari em YARN e adicionar um parâmetro nas configurações personalizadas do yarn-site:
hdp.version=2.5.3.0-37Essa mágica funcionou e o Spark levantou. Testamos alguns de nossos notebooks jupyter. Tudo funciona. Estamos prontos para a primeira aula sobre Spark no sábado (já amanhã)!
ATUALIZAÇÃO. Na aula, surgiu outro problema. Em algum momento, o YARN parou de fornecer contêineres para o Spark. No YARN, era necessário ajustar um parâmetro que estava como padrão 0.2:
yarn.scheduler.capacity.maximum-am-resource-percent=0.8 Ou seja, apenas 20% dos recursos participaram na distribuição de recursos. Alteramos os parâmetros e reiniciamos o YARN. O problema foi resolvido e os outros participantes também conseguiram iniciar o contexto do Spark.
Fonte: habr.com
