Práctica de migración fluida de ByteDance MapReduce-Spark

Resumen: este artículo está compilado del discurso de apertura de "ByteDance MapReduce - Spark Smooth Migration Practice" pronunciado por Wei Zhongjia, un ingeniero de infraestructura de ByteDance, en CommunityOverCode Asia 2023.
Con el desarrollo del negocio de bytes, la empresa ejecuta alrededor de 1 millón de trabajos Spark en línea todos los días. En contraste, todavía hay entre 20.000 y 30.000 tareas MapReduce en línea todos los días. Desde la perspectiva de los usuarios y la I+D de big data, parece que hay también una serie de problemas en el funcionamiento, mantenimiento y uso del motor MapReduce. En este contexto, el equipo de Bytedance Batch diseñó e implementó una solución para migrar sin problemas las tareas de MapReduce a Spark. Esta solución permite a los usuarios completar la migración sin problemas de MapReduce a Spark agregando solo una pequeña cantidad de parámetros o variables de entorno a los trabajos existentes. reduciendo en gran medida los costos de migración y logrando buenos beneficios de costos.

Introducción a los antecedentes

El año pasado, el número de trabajos de Bytedance Spark experimentó un fuerte aumento de 1 millón a 1,5 millones, y los datos a nivel diario de Flink Batch aumentaron de 200.000 a 250.000, mientras que el uso de MapReduce ha estado en un estado de lentitud. disminuyendo, casi desde El número ha disminuido de 14 000 a aproximadamente 10 000. Según la situación de uso anterior, MapReduce, como el marco de procesamiento por lotes de larga data que utilizamos, también ha completado su misión histórica y pronto estará fuera de línea.
Antes de desconectarlo oficialmente, primero recopilamos estadísticas sobre el lado comercial y los métodos de mantenimiento de tareas de los trabajos de tipo MapReduce.
El gráfico circular de la izquierda muestra las estadísticas de proporción del lado empresarial. La mayor proporción es el trabajo de Hadoop Streaming, que representa casi el 45% de todos los trabajos. La segunda proporción más grande es el trabajo de Druida con un 24%, y el tercero es Distcopia con 22%. La razón por la cual Distcopy y Hadoop Streaming no se dividen aquí según las líneas de negocios es porque estos dos tipos de trabajos usan exactamente el mismo código y pueden considerarse como el mismo trabajo cuando promovemos la actualización.
El gráfico circular de la derecha son las estadísticas de los métodos de mantenimiento. La mayor proporción son Otros, que representan el 60%. Otros significa trabajos que no son administrados por ninguna plataforma dentro de ByteDance. Esto también es muy consistente con las características específicas de MapReduce. Es un marco con una larga historia. Cuando se lanzaron por primera vez muchos trabajos de MapReduce, ni siquiera estas plataformas habían aparecido aún. La mayoría de ellos se enviaron directamente desde contenedores administrados por los propios usuarios o máquinas físicas que podrían conectarse al clúster YARN.
 

Por qué necesitamos promover la migración de MapReduce a Spark

Hay tres razones para utilizar MapReduce sin conexión:
La primera razón es que el modo operativo de MapReduce tiene requisitos demasiado altos en cuanto al rendimiento del motor de programación informática . En el modo operativo de MapReduce, cada Tarea corresponde a un Contenedor. Cuando la Tarea termine de ejecutarse, el Contenedor será liberado. Este modo operativo no es un problema para YARN porque el rendimiento de YARN es muy alto. Sin embargo, cuando migramos nuestro negocio interno de YARN al clúster K8, descubrimos que los trabajos de MapReduce a menudo activaban alarmas del servidor API, lo que afectaba la estabilidad del clúster K8. Después de completar una tarea de MapReduce, a menudo necesitamos solicitar más de 100,000 POD; mientras que la misma escala Un trabajo de Spark puede requerir solo unos pocos miles de POD, porque hay otra capa de programación dentro del trabajo de Spark. El contenedor aplicado por Spark como ejecutor no se iniciará después de ejecutar una tarea. En cambio, el Spark El marco programa nuevas tareas para su uso continuo.
La segunda razón es que el rendimiento aleatorio de MapReduce es muy pobre . El MapReduce utilizado internamente es la versión 2.6 basada en la comunidad. El marco Netty en el que se basa su implementación Shuffle es de hace unos diez años. En comparación con el Netty actual, existe una diferencia de versión importante. En el uso real, también encontrará su rendimiento. En comparación, el rendimiento es deficiente y también creará demasiadas conexiones en la máquina física, lo que afectará la estabilidad de la máquina física.
La tercera razón es que desde la perspectiva de los ingenieros de desarrollo, tenemos muchos proyectos de transformación horizontal internamente, como la transformación de K8 que acabamos de mencionar y la adaptación de IPV6. El costo de la transformación es en realidad el mismo que el de Spark, pero la cantidad de Tareas de MapReduce Ahora es solo el 1% de Spark. No solo el ROI de la transformación es muy bajo , sino que también requerirá mucho esfuerzo mantener el servidor histórico y el servicio aleatorio de los trabajos de MapReduce sin transformación. Por tanto, es necesario impulsar la migración de MapReduce a Spark.

Dificultades para actualizar Spark

En primer lugar, la proporción de tareas existentes es muy baja. Actualmente, sólo hay más de 10.000 puestos de trabajo por día. Sin embargo, el valor absoluto sigue siendo muy grande e involucra a muchas partes comerciales. Muchas de ellas son tareas que duran mucho tiempo. Durante mucho tiempo y es posible que se haya ejecutado cuatro veces. En cinco años, ha sido muy difícil alentar a los usuarios a actualizar activamente.
En segundo lugar, en términos de viabilidad, más de la mitad de los trabajos son trabajos de Hadoop Streaming, incluidos Shell, Python e incluso programas C ++. Aunque Spark tiene un operador Pipe, los usuarios pueden migrar trabajos existentes a Spark Pipe. mucho trabajo.
Finalmente, cuando los usuarios ayuden a iniciar la transformación, se enfrentarán muchos otros problemas. Por ejemplo, además de la migración de la lógica informática principal, hay muchas herramientas periféricas que deben migrarse; ¿cómo se deben convertir ciertos parámetros de MapReduce en durante el proceso de migración? Parámetros equivalentes de Spark y cómo implementar de manera equivalente la inyección de variables de entorno de las que depende el script de trabajo de Hadoop Streaming en Spark. Si se deja que el usuario resuelva estos problemas, no solo la carga de trabajo será pesada, sino también la falla. la tasa también será alta.
 

programa general

Objetivos de diseño

Lo anterior ha resuelto la situación actual, las motivaciones y las dificultades. Con base en la información anterior, los objetivos antes de la actualización son:
  • Esto evita que los usuarios realicen modificaciones a nivel de código y les permite completar la actualización sin moverse en absoluto, simplemente agregando algunos parámetros de trabajo.
  • Es necesario admitir varios tipos de trabajos, incluidos Hadoop Streaming, Distcp y trabajos escritos por usuarios normales en Java. Entre ellos, Hadoop Streaming usa la antigua API de MapReduce, mientras que Distcp usa la nueva API, lo que significa que nuestro plan de actualización debe admitir todos los trabajos de MapReduce.
 

Desmantelamiento de la solución

El desmantelamiento del plan general se divide principalmente en cuatro partes:
  • La adaptación del proceso informático implica principalmente alinear la lógica informática de Mapreduce y la lógica informática de Spark.
  • La adaptación de la configuración ayuda a los usuarios a convertir automáticamente los parámetros de Mapreduce en parámetros de Spark.
  • La adaptación del lado del envío es el punto clave para lograr una migración realmente fluida, de modo que los usuarios puedan completar la actualización sin modificar sus comandos de envío.
  • Coopere con herramientas para ayudar a los usuarios a verificar la exactitud de los datos.
 

Adaptación de procesos computacionales.

La captura de pantalla es del artículo (https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/mapreduce-osdi04.pdf). Un proceso clásico de MapReduce se divide en cinco pasos:
El primer paso es procesar los datos de entrada y luego segmentarlos; el segundo paso es ejecutar el código de mapa proporcionado por el usuario; el tercer paso es hacer Shuffle; el cuarto paso es ejecutar el código de reducción proporcionado por el usuario; el quinto paso es convertir el código Reducir en Los resultados del procesamiento del código se escriben en el sistema de archivos HDFS. De hecho, existe otro uso muy común de MapReduce, que es solo mapa, es decir, el uso sin los dos pasos en el medio de la figura siguiente.
Los estudiantes que estén familiarizados con Spark deben saber que todo el proceso de MapReduce puede entenderse como un subconjunto de Spark, o incluso como una tarea de Spark para un proceso de cálculo lógico específico. Hemos enumerado un pseudocódigo en la figura, que corresponde perfectamente a todo MapReduce. proceso.proceso.
El primer paso es crear un RDD de Hadoop, porque el RDD de Hadoop se basa en el propio código de formato de entrada de Hadoop, por lo que es completamente adaptable; el segundo paso es llamar al operador de mapa de Spark y luego en el operador de mapa de Spark Llame a la función de mapa del usuario; en El tercer paso, para la universalidad de la migración, utiliza el método RepartitionAndSortWithinPartitions de manera uniforme. Este método corresponde completamente al proceso Shuffle en MapReduce; el cuarto paso utiliza el operador Map para ejecutar el código Reduce proporcionado por el usuario; el quinto paso, SaveAsHadoopFile, corresponde al procedimiento almacenado final en Mapreduce.
La idea anterior es en realidad la idea convencional cuando los usuarios actualizan de MapReduce a Spark, pero si queremos diseñar una solución de actualización general, no es suficiente usar operadores Spark para mapear el proceso de cálculo de MapReduce. Mediante el análisis de los marcos MapReduce y Spark, encontramos:
La capa más baja es la misma, ambas deben depender del programador de recursos: YARN o K8. Las funciones de la capa superior son iguales o cercanas, pero la implementación es completamente diferente. Por ejemplo, los nombres en MapReduce se llaman InputFormat y OutputFormat, que en Spark se llaman HadoopRDD saveAsHadoopFile; el contador en Mareduce se llama contador, que corresponde al acumulador en Spark. Otras funciones, incluidas Shuffle, programación de recursos, historial y ejecución especulativa, están todas alineadas, pero las implementaciones también son diferentes, por lo que lo que debemos hacer es reemplazar la implementación en MapReduce con Spark.
La capa superior mencionada en el objetivo de diseño: la capa de implementación no debe cambiarse en absoluto. La capa rosa como se muestra arriba no puede ejecutarse directamente en la base de Spark, por lo que agregamos una capa intermedia para adaptarla al código del usuario y La interfaz informática de Spark utiliza MapRunner y ReduceRunner para adaptar los métodos Map y Reducer en Hadoop, de modo que el operador Map de Spark pueda ejecutar Mapper y Reducer. Adaptamos el comportamiento de llamada de Counter del usuario a través del Counter Adapter. Cuando el usuario aumenta un número a través de la interfaz de Contador, se convertirá en una llamada de Acumulador a Spark. También hay un Traductor de Couf correspondiente para la Configuración. Es decir, al enviar la tarea, se genera una Configuración de Hadoop para el usuario y traducido usando este traductor a los parámetros correspondientes de Spark. Esta es la adaptación de todo el proceso informático. A través de esta adaptación, la lógica general se puede usar para usar directamente un trabajo de Spark para ejecutar el código del usuario.

Adaptación de la configuración

Las configuraciones se pueden dividir principalmente en tres categorías: configuraciones que requieren traducción, configuraciones que se transmiten directamente de forma transparente y configuraciones que deben ignorarse.
En el primer tipo de configuración que debe traducirse, como los parámetros de la clase de recurso de trabajo, MapReduce y Spark necesitan decirle al marco de recursos qué tipo de contenedor necesito para procesar los datos, pero los parámetros que usan son diferentes. al enviar el trabajo. , los parámetros deben traducirse. También hay variables de entorno, archivos cargados, número de trabajos simultáneos, etc. en la tabla. Todos estos parámetros deben traducirse como se indicó anteriormente.
La segunda categoría es la configuración que requiere transmisión transparente directa, porque Spark necesita depender de muchas clases en Hadoop, y muchas de estas clases también deben configurarse. h adoop . Spark . Aquí podemos transmitir directamente de forma transparente agregando un prefijo durante la traducción .
La tercera categoría es la configuración que debe ignorarse, que son funciones que están disponibles en MapReduce pero no en Spark. En este caso, lo incluiremos en el manual del usuario y les informaremos que esta función no es compatible.

Adaptación del lado de la presentación

Por el bien de la experiencia del usuario, esperamos que los scripts enviados por los usuarios no necesiten modificarse en absoluto. Los trabajos aún se envían usando Hadoop y no es necesario cambiarlos a Spark Submit. Por lo tanto, en la implementación, ponemos un parche en Hadoop. Cuando se envía el trabajo MapReduce, el programa remitente identifica un parámetro específico o variable de entorno. Una vez identificado, la función de traducción de configuración que acabamos de mencionar se utilizará para Este objeto JobConf realiza la configuración traducción Una vez completada la traducción, se generará el comando de envío de Spark correspondiente para iniciar un proceso secundario para ejecutar el comando de envío de Spark.
Al mismo tiempo, MapReduce tiene una función para detectar el estado de ejecución del trabajo mediante un sondeo constante. Debido a que ahora tenemos un proceso hijo, el comportamiento de este Monitor ha cambiado de consultar el estado de una determinada ID de aplicación llamando a la interfaz RM o AM a consultar el estado del proceso hijo.

Verificación de corrección

Después de completar los tres pasos anteriores, básicamente se puede lograr una migración sin problemas, pero antes de conectarse, recomendaremos a los usuarios que realicen una verificación de doble ejecución. En realidad, todos realizan la verificación de doble ejecución, pero lo que debe mencionarse aquí es que nosotros Encontré un problema aquí. Un problema es que se deben usar dos métodos de comparación para diferentes tipos de datos. Para la mayoría de los formatos de salida, CheckSum se puede comparar directamente, pero para una pequeña cantidad de formatos de salida, se debe usar el lector de entrada correspondiente para la línea. -comparación por línea, porque algunos El archivo generado por el formato de salida contendrá marcas de tiempo o alguna información relacionada con el usuario. Los archivos generados por cada ejecución pueden ser diferentes. Si queremos comparar en este momento, debemos generar el correspondiente Lector, una línea Lea el archivo línea por línea y compárelo línea por línea.

Problemas y soluciones

Configuración de memoria: la correspondencia uno a uno entre la memoria MapReduce y la memoria Spark Executor puede activar OOM en algunos casos

Como se mencionó en la sección anterior, haremos una traducción paralela de la memoria. Por ejemplo, la tarea MapReduce original que usa memoria 4G seguirá usando memoria 4G después de convertirse a Spark, pero esto hará que muchos trabajos activen OOM. La razón principal es que los modelos de memoria de MapReduce y Spark no son exactamente iguales. El caché predeterminado para Shuffle Spill en MapReduce es 256 M, pero en Spark, un administrador de memoria en realidad administra la memoria y el uso máximo predeterminado es el 60% de la memoria total. Al mismo tiempo, el protocolo de red utilizado en MapReduce Shuffle también es diferente de Spark, lo que creará más concurrencia y utilizará más memoria.
Para resolver este problema, configuramos un parámetro Spark.memory.fraction=0.4 para todas las tareas de migración paralela para reducir la memoria durante el Shuffle Spill. Al mismo tiempo, el valor predeterminado es aumentar 512 M de memoria por núcleo. Después de esta estrategia, en línea, todo Se ha resuelto la situación de OOM de la tarea de migración sin problemas.

Configuración de simultaneidad: los trabajos de Hadoop Streaming pueden provocar conflictos en los nombres de directorio cuando se utilizan directorios locales.

Los trabajos de Spark pueden admitir múltiples tareas que se ejecutan simultáneamente en un contenedor. Algunos scripts de trabajo de HadoopStreaming crearán otro directorio en el directorio local cuando se usen. Cuando se usa Spark, varias tareas que ejecutan este directorio al mismo tiempo causarán conflictos. En MapReduce, cada tarea utilizará un contenedor nuevo, por lo que no se producirán los conflictos correspondientes.
Hay dos soluciones principales a este problema:
Primero, agregue un parámetro para controlar la concurrencia del Ejecutor del trabajo Spark actualizado. De forma predeterminada, se configura directamente para el usuario como un Ejecutor de un solo núcleo, lo que equivale a un Ejecutor que ejecuta una tarea.
En segundo lugar, se recomienda que los usuarios modifiquen la lógica de creación de su directorio. Al crear un directorio local, no cree un directorio con un nombre fijo. En su lugar, lea el ID de tarea en la variable de entorno y cree un directorio local con un ID de tarea para evitar conflictos.

Problemas de carga de clases: pueden ocurrir problemas de carga de clases cuando se ejecutan trabajos de usuario después de la actualización

Muchas tareas del paquete Jar tendrán problemas de carga de clases después de la actualización. La causa principal de este problema es que el cargador de clases Spark utiliza un ClassLoader personalizado, que divide la carga de clases en dos categorías, una es el cargador de clases Framework y la otra es el cargador de clases de usuario. Hadoop en sí es una clase en la que se basa Spark Framework, por lo que se cargará utilizando el ClassLoader de Framework. Al mismo tiempo, el código de tarea del usuario también depende de Hadoop, y algunas dependencias serán cargadas por el ClassLoader del código de usuario, por lo que Se producirán varios problemas: Problema de carga de clases.
En la mayoría de los casos, este problema se puede evitar configurando el parámetro Spark.executor.userClassPathFirst=true para que el trabajo de Spark cargue primero la clase del usuario de forma predeterminada. Sin embargo, algunos usuarios tendrán problemas después de configurar este parámetro. Esta situación se puede resolver configurándolo manualmente en False.

Problema de alineación funcional: las funciones de MapReduce no están completamente alineadas con Spark

En la práctica, a los usuarios les preocupará que algunas funciones en MapReduce no estén disponibles en Spark. Por ejemplo, MapReduce puede admitir el éxito parcial de la tarea estableciendo un parámetro. Siempre que la falla de la tarea no exceda esta proporción, todo el trabajo será exitoso, pero En Spark no existe tal función.
La solución es que, en la mayoría de los casos, los usuarios pueden resolverlo por sí mismos, pero en una pequeña cantidad de casos, si el usuario sabe que habrá archivos defectuosos en el flujo ascendente, le proporcionaremos algunos otros parámetros de Spark para evitar fallas en la tarea.

Problema de ID de intento de tarea: algunos usuarios confían en el ID de intento de tarea en la variable de entorno y no hay un valor correspondiente en Spark

El problema del ID de intento de tarea es esencialmente un problema de alineación. Algunos usuarios, especialmente los trabajos de HadoopStreaming, confían en el ID temporal de la tarea en la variable de entorno, y este valor no tiene un concepto correspondiente estricto en Spark. El ID de intento de tarea es el número de reintentos de una determinada tarea en MapReduce. En Spark, el error de reproducción aleatoria provocará un reintento de etapa en lugar de un reintento de tarea. Durante el reintento de etapa, el índice de la tarea cambiará, por lo que el número de reintentos no puede corresponder a un cierto ID de partición.
Hemos implementado una solución aproximada a este problema, utilizando otro entero positivo que aumenta globalmente (ID de intento) proporcionado en Spark Task Context para distinguir diferentes tareas para resolver el problema de valor correspondiente.
 

ingreso

Estadísticas

La solución de migración fluida anterior promueve que los usuarios actualicen de MapReduce a Spark, y el efecto general es muy bueno. La comparación de la cantidad promedio de aplicaciones de recursos de MapReduce (antes de la migración) y la cantidad de recursos de aplicaciones de Spark (después de la migración) para todas las migraciones completadas en el últimos 30 días es: ejemplo. Como se puede ver en la figura, la cantidad de aplicaciones de CPU guardadas por día es 17,000, un aumento del 60%, es decir, después de la actualización, todas estas tareas se pueden ejecutar con el 40% de los recursos originales, el uso de memoria puede Se ahorrarán unos 20.000 GB por día, aproximadamente el 17% del nivel anterior.

Interpretación

  • Como se mencionó anteriormente, esta es una solución de migración sin problemas. El usuario no usa Spark para reescribir tareas. Todo el mundo sabe que Spark es un motor estático que puede utilizar mejor la memoria, por lo que los beneficios de una migración sin problemas deberían ser menores que los de la migración manual del usuario. Porque los beneficios de la actualización actual en realidad no provienen del operador Spark en sí. De hecho, la lógica de procesamiento del usuario no ha cambiado en absoluto y el código en ejecución sigue siendo código MapReduce. Si se trata de un trabajo de Hadoop Streaming, el código en ejecución sigue siendo un script. escrito por el usuario. , por lo que este beneficio no proviene del propio operador Spark.
  • Los beneficios provienen principalmente de la etapa Shuffle, es decir, Spark Shuffle es mejor que MapReduce Shuffle desde el marco de red hasta los detalles de implementación. También hemos realizado algunas optimizaciones personalizadas en profundidad para Spark Shuffle para mejorar el rendimiento de Shuffle. Si está interesado, puede leer recomendaciones de artículos relacionados. La optimización aleatoria acorta el tiempo de ejecución de los trabajos: el tiempo promedio de mapa de algunos trabajos es de 2 minutos, el tiempo de reducción es de 5 minutos y el tiempo de reproducción aleatoria suele ser superior a 10 minutos. Después de actualizar Spark, el tiempo de reproducción aleatoria se puede reducir directamente a 0. Debido a que Spark's Shuffle es un Shuffle asincrónico, los datos se pueden calcular en el hilo principal y leer en otros hilos, reduciendo así el tiempo del bloque intermedio a milisegundos.
  • Debido a que los ingresos provienen principalmente de Shuffle, la mejora del rendimiento para los trabajos de Map Only no es obvia. Para todos los trabajos Map Only y Distcp que han completado la actualización, el volumen de la aplicación de recursos no ha cambiado mucho, oscilando entre el 90% y el 110%.
  • La razón por la que los ingresos de la CPU son significativamente mayores que los de la memoria es que la CPU es una migración completamente paralela, pero la memoria es diferente: Map y Reduce generalmente toman el valor máximo de memoria, lo que provocará un desperdicio de memoria. Además, para evitar el problema OOM causado por la migración de cuello de botella, se agregaron 512 M de memoria a cada núcleo, por lo que la cantidad total de aplicaciones de memoria aumentó. Sin embargo, debido a que los beneficios de la etapa Shuffle acortaron el tiempo de ejecución del trabajo, el memoria general Las ganancias siguen siendo positivas.
 
Broadcom anunció la terminación de la actualización de la versión deepin-IDE del programa de socios de VMware existente, una nueva apariencia. WAVE SUMMIT está celebrando su décima edición. ¡Wen Xinyiyan tendrá la última divulgación! Zhou Hongyi: El nativo de Hongmeng definitivamente tendrá éxito. El código fuente completo de GTA 5 se ha filtrado públicamente. Linus: No leeré el código en Nochebuena. Lanzaré una nueva versión del conjunto de herramientas Java Hutool-5.8.24 el año que viene. Vamos a quejarnos juntos de Furion. Exploración comercial: el barco ha pasado. Wan Zhongshan, v4.9.1.15 Apple lanza un modelo de lenguaje grande multimodal de código abierto Ferret Yakult Company confirma que se filtraron datos de 95 G
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/5941630/blog/10452051
Recomendado
Clasificación