Configuración de Spark en YARN

¡Hola, Habr! Ayer en el meetup dedicado a Apache Spark, de parte del equipo de Rambler&Co, hubo muchas preguntas de los participantes relacionadas con la configuración de esta herramienta. Decidimos compartir nuestra experiencia al respecto. El tema es complicado, por lo que también invitamos a compartir sus experiencias en los comentarios; puede que nosotros también estemos entendiendo o utilizando algo de manera incorrecta.

Una breve introducción: cómo utilizamos Spark. Tenemos un programa de tres meses “Especialista en Big Data”, y en todo el segundo módulo nuestros participantes trabajan con esta herramienta. Por lo tanto, nuestra tarea como organizadores es preparar un clúster para su uso en este contexto.

La particularidad de nuestro uso es que el número de personas que trabajan simultáneamente en Spark puede ser igual a todo el grupo. Por ejemplo, en el seminario, cuando todos prueban algo simultáneamente y siguen a nuestro instructor. Y eso no son pocos, a veces casi 40 personas. Probablemente, no haya muchas empresas en el mundo que enfrenten un escenario de uso así.

A continuación, explicaré cómo y por qué elegimos ciertos parámetros de configuración.

Comencemos desde el principio. Spark tiene 3 opciones para funcionar en un clúster: standalone, usando Mesos y usando YARN. Decidimos elegir la tercera opción, ya que era lógica para nosotros. Ya tenemos un clúster de Hadoop. Nuestros participantes ya están familiarizados con su arquitectura. Así que vamos a usar YARN.

spark.master=yarn

Luego, se pone más interesante. Cada una de estas 3 opciones de despliegue tiene 2 variantes de despliegue: client y cluster. A partir de la documentación y diferentes enlaces en Internet, se puede concluir que client es adecuado para el trabajo interactivo, por ejemplo, a través de Jupyter Notebook, mientras que cluster es más adecuado para soluciones de producción. En nuestro caso, nos interesaba el trabajo interactivo, así que:

spark.deploy-mode=client

En general, a partir de este momento, Spark ya funcionará de alguna manera en YARN, pero eso no era suficiente para nosotros. Dado que nuestro programa trata sobre grandes datos, a veces a los participantes les faltaba lo que se obtenía con un corte uniforme de recursos. Y aquí encontramos algo interesante: la asignación dinámica de recursos. En pocas palabras, la idea es la siguiente: si tienes una tarea pesada y el clúster está libre (por ejemplo, por la mañana), esta opción de Spark puede darte recursos adicionales. La necesidad se calcula mediante una fórmula ingeniosa. No entraremos en detalles; funciona bastante bien.

spark.dynamicAllocation.enabled=true

Establecimos este parámetro, y al iniciar Spark, dio un error y no se ejecutó. Correctamente, porque había que leer la documentación con más atención. Allí se indica que, para que todo esté bien, también hay que activar un parámetro adicional.

spark.shuffle.service.enabled=true

¿Para qué sirve? Cuando nuestro trabajo ya no requiere tanta cantidad de recursos, Spark debe devolverlos al pool común. La etapa más intensiva en recursos en casi cualquier tarea de MapReduce es la etapa de Shuffle. Este parámetro permite conservar los datos que se generan en esta etapa y, en consecuencia, liberar los ejecutores. Un ejecutor es un proceso que realiza todos los cálculos en el trabajador. Tiene una cierta cantidad de núcleos de procesador y una cierta cantidad de memoria.

Agregamos este parámetro. Todo parecía estar funcionando. Se notó que a los participantes realmente se les estaban otorgando más recursos cuando los necesitaban. Pero surgió otro problema: en algún momento, otros participantes se despertaban y también querían usar Spark, pero todo estaba ocupado, y estaban descontentos. Se les puede entender. Comenzamos a revisar la documentación. Ahí resultó que hay otro conjunto de parámetros que pueden influir en el proceso. Por ejemplo, si un ejecutor está en modo de espera, ¿después de cuánto tiempo se pueden recuperar los recursos?

spark.dynamicAllocation.executorIdleTimeout=120s

En nuestro caso, si tus executores no hacen nada durante dos minutos, por favor, devuélvelos al grupo general. Pero incluso este parámetro no siempre era suficiente. Era evidente que la persona no había hecho nada durante mucho tiempo, pero los recursos no se liberaban. Resultó que había otro parámetro especial: después de cuánto tiempo recoger los executores que contienen datos en caché. Por defecto, este parámetro estaba en infinito. Lo corregimos.

spark.dynamicAllocation.cachedExecutorIdleTimeout=600s

Es decir, si en 5 minutos tus executores no hacen nada, devuélvelos al grupo general. En este modo, la velocidad de liberación y entrega de recursos para un gran número de usuarios se volvió adecuada. La cantidad de quejas disminuyó. Pero decidimos ir más allá y limitar el número máximo de executores por aplicación, esencialmente por participante del programa.

spark.dynamicAllocation.maxExecutors=19

Ahora, por supuesto, aparecieron quejas desde el otro lado: "el clúster está inactivo, y solo tengo 19 executores", pero qué se le va a hacer, se necesita encontrar un equilibrio adecuado. No se puede hacer feliz a todo el mundo.

Y otra pequeña historia relacionada con la especificidad de nuestro caso. En una ocasión, algunas personas llegaron tarde a una clase práctica, y su Spark, por alguna razón, no se inició. Miramos la cantidad de recursos libres y parecía que había suficientes. Spark debería iniciar. Afortunadamente, para ese momento la documentación ya se había grabado en nuestra memoria, y recordamos que al iniciar, Spark busca un puerto para iniciar. Si el primer puerto del rango está ocupado, pasa al siguiente. Si está libre, lo ocupa. Y hay un parámetro que indica el número máximo de intentos para esto. Por defecto, es 16. Menos que el número de personas en nuestro grupo en la clase. Por lo tanto, después de 16 intentos, Spark se rendía y decía que no podía iniciar. Ajustamos ese parámetro.

spark.port.maxRetries=50

A continuación, hablaré de algunas configuraciones, que ya no están muy relacionadas con la especificidad de nuestro caso.

Para un inicio más rápido de Spark, hay una recomendación de archivar la carpeta jars, ubicada en el directorio principal de SPARK_HOME, y colocarla en HDFS. De este modo, no gastará tiempo cargando estos archivos jar a través de los trabajadores.

spark.yarn.archive=hdfs:///tmp/spark-archive.zip

También se recomienda usar kryo como serializador para un funcionamiento más rápido. Está más optimizado que el predeterminado.

spark.serializer=org.apache.spark.serializer.KryoSerializer

Y hay un problema antiguo de Spark, que a menudo se queda sin memoria. Esto ocurre frecuentemente en el momento en que los trabajadores han calculado todo y envían el resultado al driver. Aumentamos este parámetro. Por defecto es 1 GB, nosotros lo fijamos en 3.

spark.driver.maxResultSize=3072

Y por último, como postre. Cómo actualizar Spark a la versión 2.1 en la distribución de HortonWorks — HDP 2.5.3.0. Esta versión de HDP contiene la versión 2.0 preinstalada, pero en su momento decidimos que Spark está evolucionando rápidamente, y cada nueva versión corrige errores y añade capacidades adicionales, incluyendo para la API de python, por lo que decidimos que era necesario hacer la actualización.

Descargamos la versión desde el sitio oficial para Hadoop 2.7. Descomprimimos, la colocamos en la carpeta de HDP. Hicimos los enlaces simbólicos como era necesario. Al ejecutar — no arranca. Muestra un error bastante confuso.

java.lang.NoClassDefFoundError: com/sun/jersey/api/client/config/ClientConfig

Buscando en Google, descubrimos que Spark decidió no esperar a que Hadoop tuviera una actualización y decidió usar una nueva versión de jersey. Ellos mismos tienen conflictos sobre este tema en JIRA. La solución fue descargar jersey versión 1.17.1. Colocarlo en la carpeta jars de SPARK_HOME, nuevamente hacer un zip y colocar en HDFS.

Evitamos este error, pero surgió un nuevo problema que es bastante ambiguo.

org.apache.spark.SparkException: ¡La aplicación Yarn ya ha terminado! Puede que haya sido eliminada o no se pudo lanzar el maestro de la aplicación

Mientras tanto, intentamos ejecutar la versión 2.0 — todo bien. Intenta adivinar cuál es el problema. Revisamos los registros de esta aplicación y vimos algo así:

/usr/hdp/${hdp.version}/hadoop/lib/hadoop-lzo-0.6.0.${hdp.version}.jar

En resumen, por alguna razón hdp.version no se resolvía. Buscando en Google, encontramos una solución. Hay que entrar en Ambari en la configuración de YARN y agregar este parámetro en yarn-site personalizado:

hdp.version=2.5.3.0-37

Esta magia ayudó, y Spark despegó. Probamos varios de nuestros cuadernos de jupyter. Todo funciona. ¡Estamos listos para la primera clase de Spark el sábado (ya mañana)!

UPD. En la clase surgió otro problema. En algún momento YARN dejó de entregar contenedores para Spark. En YARN había que ajustar un parámetro, que por defecto estaba en 0.2:

yarn.scheduler.capacity.maximum-am-resource-percent=0.8

Es decir, solo el 20% de los recursos participaron en la distribución de recursos. Al cambiar los parámetros, reiniciamos YARN. El problema se resolvió y los demás participantes también pudieron iniciar el contexto de Spark.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster