Explicación simple de MapReduce?

166

Relacionado con mi pregunta CouchDB .

¿Alguien puede explicar MapReduce en términos que un numbnuts podría entender?

reefnet_alex
fuente
3
Y aquí está mi opinión: MapReduce para y con los niños .
Michael Hausenblas
@MichaelHausenblas - Me encanta tu ejemplo: fácil de entender y divertido para toda la familia.
Lee
Joel Spolsky tiene una buena explicación para principiantes - joelonsoftware.com/items/2006/08/01.html
user2314737

Respuestas:

187

Ir hasta los conceptos básicos de Mapa y Reducir.


El mapa es una función que "transforma" elementos en algún tipo de lista a otro tipo de elemento y los vuelve a colocar en el mismo tipo de lista.

supongamos que tengo una lista de números: [1,2,3] y quiero duplicar cada número, en este caso, la función para "duplicar cada número" es la función x = x * 2. Y sin asignaciones, podría escribir un bucle simple, digamos

A = [1, 2, 3]
foreach (item in A) A[item] = A[item] * 2

y tendría A = [2, 4, 6] pero en lugar de escribir bucles, si tuviera una función de mapa podría escribir

A = [1, 2, 3].Map(x => x * 2)

x => x * 2 es una función que se ejecutará contra los elementos en [1,2,3]. Lo que sucede es que el programa toma cada elemento, ejecuta (x => x * 2) contra él haciendo que x sea igual a cada elemento, y produce una lista de los resultados.

1 : 1 => 1 * 2 : 2  
2 : 2 => 2 * 2 : 4  
3 : 3 => 3 * 2 : 6  

así que después de ejecutar la función de mapa con (x => x * 2) tendrías [2, 4, 6].


Reducir es una función que "recopila" los elementos en listas y realiza algunos cálculos en todos ellos, reduciéndolos así a un solo valor.

Encontrar una suma o encontrar promedios son todas instancias de una función de reducción. Por ejemplo, si tiene una lista de números, digamos [7, 8, 9] y desea resumirlos, escribiría un ciclo como este

A = [7, 8, 9]
sum = 0
foreach (item in A) sum = sum + A[item]

Pero, si tiene acceso a una función de reducción, puede escribirla así

A = [7, 8, 9]
sum = A.reduce( 0, (x, y) => x + y )

Ahora es un poco confuso por qué se pasan 2 argumentos (0 y la función con x e y). Para que una función de reducción sea útil, debe ser capaz de tomar 2 elementos, calcular algo y "reducir" esos 2 elementos a un solo valor, por lo que el programa podría reducir cada par hasta que tengamos un solo valor.

la ejecución seguiría:

result = 0
7 : result = result + 7 = 0 + 7 = 7
8 : result = result + 8 = 7 + 8 = 15
9 : result = result + 9 = 15 + 9 = 24

Pero no desea comenzar con ceros todo el tiempo, por lo que el primer argumento está ahí para permitirle especificar un valor semilla específicamente el valor en la primera result =línea.

Digamos que desea sumar 2 listas, podría verse así:

A = [7, 8, 9]
B = [1, 2, 3]
sum = 0
sum = A.reduce( sum, (x, y) => x + y )
sum = B.reduce( sum, (x, y) => x + y )

o una versión que es más probable que encuentres en el mundo real:

A = [7, 8, 9]
B = [1, 2, 3]

sum_func = (x, y) => x + y
sum = A.reduce( B.reduce( 0, sum_func ), sum_func )

Es algo bueno en un software de base de datos porque, con el soporte Map \ Reduce, puede trabajar con la base de datos sin necesidad de saber cómo se almacenan los datos en una base de datos para usarla, para eso sirve un motor de base de datos.

Solo necesita poder "decirle" al motor lo que desea al proporcionarle una función de Mapa o Reducir y luego el motor de base de datos podría orientarse alrededor de los datos, aplicar su función y obtener los resultados quiero todo sin que sepas cómo se repite en todos los registros.

Hay índices, claves, uniones, vistas y muchas cosas que una sola base de datos puede contener, por lo que al protegerlo de cómo se almacenan realmente los datos, su código se hace más fácil de escribir y mantener.

Lo mismo ocurre con la programación paralela, si solo especifica lo que desea hacer con los datos en lugar de implementar realmente el código de bucle, entonces la infraestructura subyacente podría "paralelizarse" y ejecutar su función en un bucle paralelo simultáneo para usted.

chakrit
fuente
Ok, entiendo el mapa y reduzco tomado individualmente. ¿Pero qué aplicaciones podría tener de la reducción? En un escenario de Google, ¿lo usarían, por ejemplo, para sumar una serie de parámetros que les dan la clasificación de una página para una palabra clave determinada?
Lorenzo
@lbolognini var total = orderes.Sum (o => o.UnitPrice * o.Quantity)
chakrit el
@lbolognini Hay muchos usos cuando abstrae el concepto mismo de bucle. En el escenario de Google, probablemente tengan miles de máquinas para calcular rangos de páginas, enlaces y demás. ¿Qué hacen cuando necesitan agregar algunos servidores más? La modificación de cada código de bucle probablemente no sea una opción. Entonces, lo que hicieron fue escribir su código de cálculo contra una función "Reducir" ... Y cuando la lista de servidores cambia, solo la función "Reducir" necesita ser cambiada. ¿Entendido?
chakrit
¿Cómo reduciría el cálculo del promedio? por lo que veo, supongo que no podrías tal vez mapear el numerador y el denominador y dividir al final de sumar ambos?
andyczerwonka
@arcticpenguin Estoy siendo un poco genérico allí. En realidad, Average()supuestamente es la formación de hielo encima Sum(). Pero hablé sobre eso para ilustrar por qué la función se llama "Reducir" ... Una función promedio es algo que toma una lista de números y la reduce a un solo número (que es el promedio).
chakrit
60

MapReduce es un método para procesar grandes sumas de datos en paralelo sin requerir que el desarrollador escriba ningún otro código que no sea el mapeador y reduzca las funciones.

La función de mapa toma datos y produce un resultado, que se mantiene en una barrera. Esta función puede ejecutarse en paralelo con una gran cantidad de la misma tarea de mapa . El conjunto de datos se puede reducir a un valor escalar.

Entonces, si piensas en ello como una declaración SQL

SELECT SUM(salary)
FROM employees
WHERE salary > 1000
GROUP by deptname

Podemos usar el mapa para obtener nuestro subconjunto de empleados con salario> 1000, que el mapa emite a la barrera en grupos de tamaño de grupo.

Reducir sumará cada uno de esos grupos. Dándote tu conjunto de resultados.

Acabo de sacar esto de mis notas de estudio de la universidad del artículo de Google

Johnno Nolan
fuente
33
  1. Toma un montón de datos
  2. Realizar algún tipo de transformación que convierta cada dato en otro tipo de dato
  3. Combina esos nuevos datos en datos aún más simples

El paso 2 es el mapa. El paso 3 es reducir.

Por ejemplo,

  1. Obtenga tiempo entre dos impulsos en un par de medidores de presión en la carretera
  2. Mapee esos tiempos en velocidades basadas en la distancia de los medidores
  3. Reduce esas velocidades a una velocidad promedio

La razón por la que MapReduce se divide entre Map y Reduce es porque diferentes partes se pueden hacer fácilmente en paralelo. (Especialmente si Reduce tiene ciertas propiedades matemáticas).

Para obtener una descripción compleja pero buena de MapReduce, consulte: Modelo de programación MapReduce de Google - Revisited (PDF) .

Frank Krueger
fuente
1
Yo diría para el paso 3, "combinar" en lugar de "transformar"
TraumaPony
La primera vez, tres respuestas combinadas es la MEJOR respuesta. Lea primero el enlace del artículo de Nasser (nivel teórico alto) Luego la respuesta de chakrit (explicación individual de map-reduce) Ahora la respuesta de Frank (¿Cuál es el famoso idioma de MapReduce?) Gracias a ustedes tres. :)
Ajeet Ganga
20

MAP y REDUCE son funciones antiguas de Lisp de una época en que el hombre mató a los últimos dinosaurios.

Imagine que tiene una lista de ciudades con información sobre el nombre, la cantidad de personas que viven allí y el tamaño de la ciudad:

(defparameter *cities*
  '((a :people 100000 :size 200)
    (b :people 200000 :size 300)
    (c :people 150000 :size 210)))

Ahora es posible que desee encontrar la ciudad con la mayor densidad de población.

Primero creamos una lista de nombres de ciudades y densidad de población usando MAP:

(map 'list
     (lambda (city)
         (list (first city)
               (/ (getf (rest city) :people)
                  (getf (rest city) :size))))
     *cities*)

=>   ((A 500) (B 2000/3) (C 5000/7))

Usando REDUCIR ahora podemos encontrar la ciudad con la mayor densidad de población.

(reduce (lambda (a b)
          (if (> (second a) (second b))
             a
             b))
        '((A 500) (B 2000/3) (C 5000/7)))

 =>   (C 5000/7)

Combinando ambas partes obtenemos el siguiente código:

(reduce (lambda (a b)
          (if (> (second a) (second b))
             a
             b))
        (map 'list
             (lambda (city)
                (list (first city)
                   (/ (getf (rest city) :people)
                      (getf (rest city) :size))))
             *cities*))

Vamos a introducir funciones:

(defun density (city)
   (list (first city)
         (/ (getf (rest city) :people)
            (getf (rest city) :size))))

(defun max-density (a b)
   (if (> (second a) (second b))
          a
          b))

Entonces podemos escribir nuestro código REDUCE MAPA como:

(reduce 'max-density
        (map 'list 'density *cities*))

 =>   (C 5000/7)

Llama MAPy REDUCE(la evaluación está al revés), por lo que se llama reducción de mapa .

Rainer Joswig
fuente
@MoMolog: la función MAX ya existe y hace algo ligeramente diferente. Además: uno no debe redefinir MAX.
Rainer Joswig
max-densitycompara el segundo elemento de los argumentos pasados, ¿verdad? Perdón por la tonta edición.
Alexander Presber
@MoMolog: sí, es el segundo elemento y eso solo es útil en el contexto de este pequeño ejemplo. El código también está escrito a propósito en Lisp un poco antiguo con listas como estructuras de datos ...
Rainer Joswig
17

Tomemos el ejemplo del artículo de Google . El objetivo de MapReduce es poder usar eficientemente una carga de unidades de procesamiento que trabajen en paralelo para algún tipo de algoritmos. El ejemplo es el siguiente: desea extraer todas las palabras y su recuento en un conjunto de documentos.

Implementación típica:

for each document
    for each word in the document
        get the counter associated to the word for the document
        increment that counter 
    end for
end for

Implementación de MapReduce:

Map phase (input: document key, document)
for each word in the document
    emit an event with the word as the key and the value "1"
end for

Reduce phase (input: key (a word), an iterator going through the emitted values)
for each value in the iterator
    sum up the value in a counter
end for

Alrededor de eso, tendrá un programa maestro que dividirá el conjunto de documentos en "divisiones" que se manejarán en paralelo para la fase de Mapa. Los valores emitidos son escritos por el trabajador en un búfer específico para el trabajador. El programa maestro luego delega a otros trabajadores para realizar la fase de reducción tan pronto como se le notifica que el búfer está listo para ser manejado.

Cada salida de trabajador (que es un trabajador de Map o Reduce) es, de hecho, un archivo almacenado en el sistema de archivos distribuido (GFS para Google) o en la base de datos distribuida para CouchDB.

Damien B
fuente
10

Una introducción realmente fácil , rápida y "para tontos" a MapReduce está disponible en: http://www.marcolotz.com/?p=67

Publicando parte de su contenido:

En primer lugar, ¿por qué se creó originalmente MapReduce?

Básicamente, Google necesitaba una solución para hacer que los grandes trabajos de computación fueran fácilmente paralelizables, permitiendo que los datos se distribuyan en varias máquinas conectadas a través de una red. Aparte de eso, tuvo que manejar la falla de la máquina de manera transparente y gestionar los problemas de equilibrio de carga.

¿Cuáles son las verdaderas fortalezas de MapReduce?

Se puede decir que la magia MapReduce se basa en la aplicación de funciones Map and Reduce. Debo confesar amigo, que estoy totalmente en desacuerdo. La característica principal que hizo que MapReduce fuera tan popular es su capacidad de paralelización y distribución automáticas, combinadas con la interfaz simple. Estos factores sumados al manejo transparente de fallas para la mayoría de los errores hicieron que este marco fuera tan popular.

Un poco más de profundidad en el papel:

MapReduce se mencionó originalmente en un documento de Google (Dean y Ghemawat, 2004 - enlace aquí) como una solución para realizar cálculos en Big Data utilizando un enfoque paralelo y grupos de computadoras comerciales. A diferencia de Hadoop, que está escrito en Java, el marco de trabajo de Google está escrito en C ++. El documento describe cómo se comportaría un marco paralelo utilizando las funciones de Mapa y Reducir de la programación funcional en grandes conjuntos de datos.

En esta solución, habría dos pasos principales, llamados Mapa y Reducir, con un paso opcional entre el primero y el segundo, llamado Combinar. El paso de Mapa se ejecutaría primero, haría cálculos en el par clave-valor de entrada y generaría un nuevo valor clave de salida. Hay que tener en cuenta que el formato de los pares clave-valor de entrada no necesita coincidir necesariamente con el par de formato de salida. El paso Reducir reuniría todos los valores de la misma clave, realizando otros cálculos sobre ella. Como resultado, este último paso generaría pares clave-valor. Una de las aplicaciones más triviales de MapReduce es implementar el conteo de palabras.

El pseudocódigo para esta aplicación se proporciona a continuación:

map(String key, String value):

// key: document name
// value: document contents
for each word w in value:
EmitIntermediate(w, “1”);

reduce(String key, Iterator values):

// key: a word
// values: a list of counts
int result = 0;
for each v in values:
    result += ParseInt(v);
Emit(AsString(result));

Como se puede notar, el mapa lee todas las palabras en un registro (en este caso, un registro puede ser una línea) y emite la palabra como una clave y el número 1 como un valor. Más adelante, la reducción agrupará todos los valores de la misma clave. Pongamos un ejemplo: imagine que la palabra 'casa' aparece tres veces en el registro. La entrada del reductor sería [casa, [1,1,1]]. En el reductor, sumará todos los valores para la casa clave y dará como resultado el siguiente valor clave: [casa, [3]].

Aquí hay una imagen de cómo se vería esto en un marco de MapReduce:

Imagen del documento original de Google MapReduce

Como algunos otros ejemplos clásicos de aplicaciones MapReduce, se puede decir:

• Recuento de frecuencia de acceso URL

• Gráfico de enlace web inverso

• Grep distribuido

• Vector de término por host

Para evitar demasiado tráfico de red, el documento describe cómo el marco debe tratar de mantener la localidad de datos. Esto significa que siempre debe intentar asegurarse de que una máquina que ejecuta trabajos de Map tenga los datos en su memoria / almacenamiento local, evitando obtenerlos de la red. Con el objetivo de reducir la red mediante la colocación de un mapeador, se utiliza el paso combinador opcional, descrito anteriormente. El Combinador realiza cálculos en la salida de los mapeadores en una máquina determinada antes de enviarlo a los Reductores, que pueden estar en otra máquina.

El documento también describe cómo deben comportarse los elementos del marco en caso de fallas. Estos elementos, en el documento, se denominan trabajador y maestro. Se dividirán en elementos más específicos en implementaciones de código abierto. Dado que Google solo describió el enfoque en el documento y no lanzó su software propietario, se crearon muchos marcos de código abierto para implementar el modelo. Como ejemplos, se puede decir Hadoop o la función limitada MapReduce en MongoDB.

El tiempo de ejecución debe ocuparse de los detalles de los programadores no expertos, como particionar los datos de entrada, programar la ejecución del programa en el gran conjunto de máquinas, manejar las fallas de las máquinas (de manera transparente, por supuesto) y administrar la comunicación entre máquinas. . Un usuario experimentado puede ajustar estos parámetros, como cómo se dividirán los datos de entrada entre los trabajadores.

Conceptos clave:

Tolerancia a fallas:Debe tolerar la falla de la máquina con gracia. Para realizar esto, el maestro toca periódicamente a los trabajadores. Si el maestro no recibe respuestas de un trabajador determinado en un lapso de tiempo definido, el maestro definirá el trabajo como fallido en ese trabajador. En este caso, todas las tareas de mapa completadas por el trabajador defectuoso se descartan y se entregan a otro trabajador disponible. Similar sucede si el trabajador todavía estaba procesando un mapa o una tarea de reducción. Tenga en cuenta que si el trabajador ya completó su parte de reducción, todos los cálculos ya habían finalizado cuando fallaron y no es necesario restablecerlos. Como punto primario de falla, si el maestro falla, todo el trabajo falla. Por esta razón, uno puede definir puntos de control periódicos para el maestro, con el fin de guardar su estructura de datos.

Localidad: para evitar el tráfico de red, el marco intenta asegurarse de que todos los datos de entrada estén disponibles localmente para las máquinas que van a realizar cálculos en ellos. En la descripción original, utiliza el Sistema de archivos de Google (GFS) con un factor de replicación establecido en 3 y tamaños de bloque de 64 MB. Esto significa que el mismo bloque de 64 MB (que compone un archivo en el sistema de archivos) tendrá copias idénticas en tres máquinas diferentes. El maestro sabe dónde están los bloques e intenta programar trabajos de mapas en esa máquina. Si eso falla, el maestro intenta asignar una máquina cerca de una réplica de los datos de entrada de tareas (es decir, una máquina de trabajo en el mismo bastidor de la máquina de datos).

Granularidad de tareas: suponiendo que cada fase del mapa se divida en M piezas y que cada fase Reducir se divida en R, lo ideal sería que M y R sean mucho más grandes que la cantidad de máquinas de trabajo. Esto se debe al hecho de que un trabajador que realiza muchas tareas diferentes mejora el equilibrio dinámico de carga. Aparte de eso, aumenta la velocidad de recuperación en caso de falla del trabajador (ya que las muchas tareas de mapeo que ha completado pueden extenderse a todas las otras máquinas).

Tareas de respaldo: a veces, un trabajador de Map o Reducer puede comportarse mucho más lento que los demás en el clúster. Esto puede contener el tiempo total de procesamiento y hacer que sea igual al tiempo de procesamiento de esa única máquina lenta. El documento original describe una alternativa llamada Tareas de respaldo, que el maestro programa cuando una operación de MapReduce está a punto de completarse. Estas son tareas programadas por el maestro de las tareas en curso. Por lo tanto, la operación MapReduce se completa cuando finaliza el primario o el de respaldo.

Contadores: a veces uno puede desear contar eventos ocurridos. Por esta razón, cuenta donde se creó. Los valores de contador en cada trabajador se propagan periódicamente al maestro. El maestro luego agrega (Sí. Parece que los agregadores de Pregel vinieron de este lugar) los valores de contador de un mapa exitoso y reducen la tarea y los devuelven al código de usuario cuando se completa la operación MapReduce. También hay un valor de contador actual disponible en el estado maestro, por lo que un humano que observa el proceso puede realizar un seguimiento de cómo se está comportando.

Bueno, supongo que con todos los conceptos anteriores, Hadoop será pan comido para ti. Si tiene alguna pregunta sobre el artículo original de MapReduce o algo relacionado, hágamelo saber.

Prometeo
fuente
4

No quiero sonar trillado, pero esto me ayudó mucho, y es bastante simple:

cat input | map | reduce > output
Mike Dewar
fuente
4

Si está familiarizado con Python, la siguiente es la explicación más simple posible de MapReduce:

In [2]: data = [1, 2, 3, 4, 5, 6]
In [3]: mapped_result = map(lambda x: x*2, data)

In [4]: mapped_result
Out[4]: [2, 4, 6, 8, 10, 12]

In [10]: final_result = reduce(lambda x, y: x+y, mapped_result)

In [11]: final_result
Out[11]: 42

Vea cómo cada segmento de datos sin procesar se procesó individualmente, en este caso, multiplicado por 2 (la parte del mapa de MapReduce). Sobre la base de la mapped_result, llegamos a la conclusión de que el resultado sería 42(al reducir la parte de MapReduce).

Una conclusión importante de este ejemplo es el hecho de que cada parte del procesamiento no depende de otra parte. Por ejemplo, si los thread_1mapas [1, 2, 3]y los thread_2mapas [4, 5, 6], el resultado final de ambos subprocesos seguiría siendo, [2, 4, 6, 8, 10, 12]pero hemos reducido a la mitad el tiempo de procesamiento para esto. Lo mismo puede decirse de la operación de reducción y es la esencia de cómo funciona MapReduce en la computación paralela.

Rafay
fuente