Semplice spiegazione di MapReduce?


166

Relativo alla mia domanda CouchDB .

Qualcuno può spiegare MapReduce in termini comprensibili a numbnuts?




@MichaelHausenblas - Adoro il tuo esempio: facile da capire e divertente per tutta la famiglia.
Lee,

Joel Spolsky ha una buona spiegazione per i principianti - joelonsoftware.com/items/2006/08/01.html
user2314737

Risposte:


187

Scendendo fino alle basi per Mappa e Riduci.


Mappa è una funzione che "trasforma" gli elementi in un tipo di elenco in un altro tipo di oggetto e li riporta nello stesso tipo di elenco.

supponiamo di avere un elenco di numeri: [1,2,3] e voglio raddoppiare ogni numero, in questo caso la funzione per "raddoppiare ogni numero" è la funzione x = x * 2. E senza mappature, potrei scrivere un semplice ciclo, diciamo

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

e avrei A = [2, 4, 6] ma invece di scrivere loop, se avessi una funzione di mappa potrei scrivere

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

x => x * 2 è una funzione da eseguire contro gli elementi in [1,2,3]. Quello che succede è che il programma prende ogni elemento, esegue (x => x * 2) contro di esso facendo x uguale a ciascun elemento e produce un elenco dei risultati.

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

quindi dopo aver eseguito la funzione map con (x => x * 2) avresti [2, 4, 6].


Ridurre è una funzione che "raccoglie" gli elementi negli elenchi ed esegue alcuni calcoli su tutti , riducendoli così a un singolo valore.

Trovare una somma o trovare le medie sono tutti esempi di una funzione di riduzione. Ad esempio, se hai un elenco di numeri, ad esempio [7, 8, 9] e vuoi che siano riassunti, scriverai un ciclo come questo

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

Ma se hai accesso a una funzione di riduzione, puoi scriverla in questo modo

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

Ora è un po 'confuso il motivo per cui sono stati passati 2 argomenti (0 e la funzione con xey). Affinché una funzione di riduzione sia utile, deve essere in grado di prendere 2 elementi, calcolare qualcosa e "ridurre" quei 2 elementi a un solo valore, quindi il programma potrebbe ridurre ogni coppia fino a quando non abbiamo un singolo valore.

l'esecuzione sarebbe seguita:

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

Ma non vuoi iniziare sempre con zero, quindi il primo argomento è lì per permetterti di specificare un valore seed specificamente il valore nella prima result =riga.

dì che vuoi sommare 2 elenchi, potrebbe apparire così:

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 versione che avresti più probabilità di trovare nel mondo reale:

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

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

È positivo in un software DB perché, con il supporto Map \ Reduce, è possibile lavorare con il database senza la necessità di sapere come i dati sono archiviati in un DB per utilizzarlo, ecco a cosa serve un motore DB.

Devi solo essere in grado di "dire" al motore ciò che desideri fornendo loro una funzione Map o Reduce e quindi il motore DB potrebbe orientarsi tra i dati, applicare la tua funzione e ottenere i risultati che vuoi tutto senza che tu sappia come passa su tutti i record.

Ci sono indici, chiavi, join e viste e molte cose che un singolo database potrebbe contenere, quindi proteggendoti dal modo in cui i dati sono effettivamente memorizzati, il tuo codice è più facile da scrivere e gestire.

Lo stesso vale per la programmazione parallela, se specifichi solo cosa vuoi fare con i dati invece di implementare effettivamente il codice di looping, l'infrastruttura sottostante potrebbe "parallelizzare" ed eseguire la tua funzione in un loop parallelo simultaneo per te.


Ok, capisco la mappa e la riduzione presa individualmente. Ma quali applicazioni potrei avere della riduzione? In uno scenario di Google lo userebbero ad esempio per sommare una serie di parametri che danno loro il posizionamento di una pagina per una determinata parola chiave?
Lorenzo,

@lbolognini var total = orderes.Sum (o => o.UnitPrice * o.Quantity)
chakrit

@lbolognini Ci sono molti usi per sottrarre il concetto stesso di looping. Nello scenario di Google probabilmente hanno migliaia di macchine per il calcolo di pagerank, link e quant'altro. Cosa fanno quando devono aggiungere altri server? La modifica di ogni singolo codice di loop non è probabilmente un'opzione. Quindi quello che hanno fatto è che hanno scritto il loro codice di calcolo contro una funzione "Riduci" invece ... E quando l'elenco dei server cambia, solo la funzione "Riduci" deve essere cambiata. Fatto?
chakrit,

come ridurrebbe calcolare la media? da quello che vedo, immagino che non sia possibile? magari mappare il numeratore e il denominatore e dividerli alla fine della somma di entrambi?
andyczerwonka,

@arcticpenguin Sono un po 'troppo generico lì. In realtà Average()è presumibilmente la ciliegina sulla torta Sum(). Ma ne ho parlato per illustrare perché la funzione si chiama "Riduci" ... Una funzione media è qualcosa che prende un elenco di numeri e lo riduce a un singolo numero (che è la media).
Chakrit,

60

MapReduce è un metodo per elaborare grandi somme di dati in parallelo senza richiedere allo sviluppatore di scrivere qualsiasi altro codice diverso dal mapper e ridurre le funzioni.

La funzione mappa acquisisce i dati e produce un risultato, che è tenuto in una barriera. Questa funzione può essere eseguita in parallelo con un gran numero della stessa attività della mappa . Il set di dati può quindi essere ridotto a un valore scalare.

Quindi, se ci pensi come un'istruzione SQL

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

Possiamo usare la mappa per ottenere il nostro sottogruppo di dipendenti con stipendio> 1000 che la mappa emette alla barriera in secchi di dimensioni di gruppo.

Riduci sommerà ciascuno di questi gruppi. Ti dà il tuo set di risultati.

l'ho appena estratto dalle mie note di studio universitarie del documento di Google


33
  1. Prendi un sacco di dati
  2. Eseguire un tipo di trasformazione che converte ogni dato in un altro tipo di dato
  3. Combina questi nuovi dati in dati ancora più semplici

Il passaggio 2 è Mappa. Il passaggio 3 è Riduci.

Per esempio,

  1. Prendi tempo tra due impulsi su un paio di misuratori di pressione sulla strada
  2. Mappa quei tempi in velocità in base alla distanza dei metri
  3. Ridurre quelle velocità a una velocità media

Il motivo per cui MapReduce è diviso tra Map e Reduce è perché diverse parti possono essere facilmente eseguite in parallelo. (Soprattutto se Reduce ha determinate proprietà matematiche.)

Per una descrizione complessa ma buona di MapReduce, consultare: Modello di programmazione MapReduce di Google - Revisited (PDF) .


1
Vorrei dire per il passaggio 3, "combinare" invece di "trasformare"
TraumaPony,

La prima volta, tre risposte combinate è la risposta MIGLIORE. Leggi prima il link all'articolo di Nasser (livello teorico) Quindi la risposta di Chakrit (spiegazione individuale della riduzione della mappa) Ora la risposta di Frank (Qual è il famoso linguaggio di MapReduce.) Grazie a voi tre. :)
Ajeet Ganga

20

MAP e REDUCE sono vecchie funzioni di Lisp di un tempo in cui l'uomo ha ucciso gli ultimi dinosauri.

Immagina di avere un elenco di città con informazioni sul nome, il numero di persone che vivono lì e la dimensione della città:

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

Ora potresti voler trovare la città con la più alta densità di popolazione.

Innanzitutto creiamo un elenco di nomi di città e densità di popolazione 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))

Con REDUCE ora possiamo trovare la città con la maggiore densità di popolazione.

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

 =>   (C 5000/7)

Combinando entrambe le parti otteniamo il seguente codice:

(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*))

Introduciamo funzioni:

(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))

Quindi possiamo scrivere il nostro codice MAP REDUCE come:

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

 =>   (C 5000/7)

Chiama MAPe REDUCE(la valutazione è capovolta), quindi si chiama riduci mappa .


@MoMolog: la funzione MAX esiste già e fa qualcosa di leggermente diverso. Inoltre: non si dovrebbe ridefinire MAX.
Rainer Joswig,

max-densityconfronta il secondo elemento degli arg passati, giusto? Ci scusiamo per la modifica sciocca.
Alexander Presber,

@MoMolog: sì, è il secondo elemento ed è utile solo nel contesto di questo piccolo esempio. Il codice è anche appositamente scritto in Lisp leggermente vecchio stile con elenchi come strutture di dati ...
Rainer Joswig

17

Facciamo l'esempio dal documento di Google . L'obiettivo di MapReduce è di essere in grado di utilizzare in modo efficiente un carico di unità di elaborazione che lavorano in parallelo per alcuni tipi di algoritmi. L'esempio è il seguente: si desidera estrarre tutte le parole e il loro conteggio in una serie di documenti.

Implementazione tipica:

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

Implementazione di 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

Attorno a questo, avrai un programma master che partizionerà l'insieme di documenti in "split" che saranno gestiti in parallelo per la fase della mappa. I valori emessi vengono scritti dal lavoratore in un buffer specifico per il lavoratore. Il programma principale, quindi, delega gli altri lavoratori all'esecuzione della fase di riduzione non appena viene notificato che il buffer è pronto per essere gestito.

Ogni output di lavoratore (essendo una mappa o un lavoratore di riduzione) è in realtà un file archiviato nel file system distribuito (GFS per Google) o nel database distribuito per CouchDB.


10

Un'introduzione davvero semplice , rapida e "per i manichini" a MapReduce è disponibile all'indirizzo: http://www.marcolotz.com/?p=67

Pubblicando parte del suo contenuto:

Prima di tutto, perché MapReduce è stato originariamente creato?

Fondamentalmente Google aveva bisogno di una soluzione per rendere facilmente parallelizzabili i lavori di calcolo di grandi dimensioni, consentendo di distribuire i dati in un numero di macchine connesse tramite una rete. A parte ciò, ha dovuto gestire i guasti della macchina in modo trasparente e gestire i problemi di bilanciamento del carico.

Quali sono i veri punti di forza di MapReduce?

Si potrebbe dire che la magia di MapReduce si basa sull'applicazione delle funzioni Mappa e Riduzione. Devo confessare, amico, che non sono assolutamente d'accordo. La caratteristica principale che ha reso MapReduce così popolare è la sua capacità di parallelizzazione e distribuzione automatiche, combinata con la semplice interfaccia. Questi fattori riassunti con una gestione trasparente degli errori per la maggior parte degli errori hanno reso questo framework così popolare.

Un po 'più di profondità sulla carta:

MapReduce è stato originariamente menzionato in un articolo di Google (Dean & Ghemawat, 2004 - link qui) come soluzione per effettuare calcoli nei Big Data utilizzando un approccio parallelo e cluster di prodotti di base. Contrariamente a Hadoop, che è scritto in Java, il framework di Google è scritto in C ++. Il documento descrive come si comporterebbe un framework parallelo utilizzando le funzioni Mappa e Riduci dalla programmazione funzionale su set di dati di grandi dimensioni.

In questa soluzione ci sarebbero due passaggi principali - chiamati Mappa e Riduci -, con un passaggio opzionale tra il primo e il secondo - chiamato Combina. Il passaggio Mappa verrebbe eseguito per primo, eseguendo i calcoli nella coppia chiave-valore di input e generando un nuovo valore-chiave di output. Bisogna tenere presente che il formato delle coppie chiave-valore di input non deve necessariamente corrispondere alla coppia di formati di output. Il passaggio Riduci assemblava tutti i valori della stessa chiave, eseguendo altri calcoli su di essa. Di conseguenza, quest'ultimo passaggio produrrebbe coppie chiave-valore. Una delle applicazioni più banali di MapReduce è implementare il conteggio delle parole.

Lo pseudo-codice per questa applicazione è indicato di seguito:

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));

Come si può notare, la mappa legge tutte le parole in un record (in questo caso un record può essere una riga) ed emette la parola come chiave e il numero 1 come valore. Successivamente, la riduzione raggrupperà tutti i valori della stessa chiave. Facciamo un esempio: immagina che la parola "casa" appaia tre volte nel documento. L'input del riduttore sarebbe [house, [1,1,1]]. Nel riduttore, sommerà tutti i valori per la chiave della casa e fornirà come output il seguente valore chiave: [casa, [3]].

Ecco un'immagine di come sarebbe simile in un framework MapReduce:

Immagine dalla carta originale di Google Reduce

Come alcuni altri esempi classici di applicazioni MapReduce, si può dire:

• Conteggio della frequenza di accesso all'URL

• Grafico di collegamento Web inverso

• Grep distribuito

• Termine Vector per host

Al fine di evitare un eccessivo traffico di rete, il documento descrive come il framework dovrebbe cercare di mantenere la localizzazione dei dati. Ciò significa che dovrebbe sempre cercare di assicurarsi che una macchina che esegue i lavori della mappa abbia i dati nella sua memoria / memoria locale, evitando di recuperarli dalla rete. Al fine di ridurre la rete tramite l'inserimento di un mappatore, viene utilizzato il passaggio combinatore opzionale, descritto in precedenza. Il Combiner esegue calcoli sull'output dei mappatori in una data macchina prima di inviarlo ai Riduttori, che possono trovarsi in un'altra macchina.

Il documento descrive inoltre come dovrebbero comportarsi gli elementi del framework in caso di guasti. Questi elementi, nel documento, sono chiamati come lavoratore e padrone. Saranno suddivisi in elementi più specifici nelle implementazioni open source. Poiché Google ha descritto solo l'approccio nel documento e non ha rilasciato il suo software proprietario, sono stati creati molti framework open source per implementare il modello. Ad esempio, si potrebbe dire Hadoop o la funzione MapReduce limitata in MongoDB.

Il runtime dovrebbe occuparsi dei dettagli dei programmatori non esperti, come il partizionamento dei dati di input, la pianificazione dell'esecuzione del programma su un ampio set di macchine, la gestione dei guasti delle macchine (in modo trasparente, ovviamente) e la gestione della comunicazione tra macchine . Un utente esperto può ottimizzare questi parametri, come il modo in cui i dati di input verranno suddivisi tra i lavoratori.

Concetti chiave:

Tolleranza ai guasti:Deve tollerare il malfunzionamento della macchina con garbo. Per eseguire ciò, il maestro esegue periodicamente il ping dei lavoratori. Se il master non riceve risposte da un determinato lavoratore in un determinato intervallo di tempo, il master definirà il lavoro come non riuscito in quel lavoratore. In questo caso, tutte le attività della mappa completate dal lavoratore difettoso vengono eliminate e assegnate a un altro lavoratore disponibile. Simile succede se il lavoratore stava ancora elaborando una mappa o un'attività di riduzione. Si noti che se il lavoratore ha già completato la sua parte di riduzione, tutto il calcolo era già terminato quando non è riuscito e non è necessario ripristinarlo. Come principale punto di errore, se il master fallisce, tutto il lavoro fallisce. Per questo motivo, è possibile definire punti di controllo periodici per il master, al fine di salvare la sua struttura di dati.

Località: al fine di evitare il traffico di rete, il framework cerca di assicurarsi che tutti i dati di input siano disponibili localmente per le macchine che eseguiranno calcoli su di essi. Nella descrizione originale, utilizza Google File System (GFS) con fattore di replica impostato su 3 e dimensioni del blocco di 64 MB. Ciò significa che lo stesso blocco di 64 MB (che compone un file nel file system) avrà copie identiche in tre macchine diverse. Il master sa dove sono i blocchi e prova a pianificare i lavori della mappa in quella macchina. In caso contrario, il master tenta di allocare una macchina vicino a una replica dei dati di input delle attività (ovvero una macchina worker nello stesso rack della macchina dati).

Granularità del compito: supponendo che ogni fase della mappa sia divisa in pezzi M e che ogni fase di riduzione sia divisa in pezzi R, l'ideale sarebbe che M e R siano molto più grandi del numero di macchine operatrici. Ciò è dovuto al fatto che un lavoratore che esegue molte attività diverse migliora il bilanciamento del carico dinamico. A parte ciò, aumenta la velocità di recupero in caso di errore del lavoratore (poiché le numerose attività di mappatura che ha completato possono essere distribuite su tutte le altre macchine).

Attività di backup: a volte un lavoratore di Map o Reducer può comportarsi in modo molto più lento rispetto agli altri nel cluster. Ciò può contenere il tempo di elaborazione totale e renderlo uguale al tempo di elaborazione di quella singola macchina lenta. L'articolo originale descrive un'alternativa chiamata Attività di backup, che sono programmate dal master quando un'operazione MapReduce è prossima al completamento. Queste sono attività pianificate dal Master delle attività in corso. Pertanto, l'operazione MapReduce viene completata al termine del backup primario o.

Contatori: a volte si potrebbe desiderare di contare le occorrenze di eventi. Per questo motivo, conta dove è stato creato. I valori dei contatori in ciascun lavoratore vengono periodicamente propagati al master. Il master quindi aggrega (Sì. Sembra che gli aggregatori Pregel provengano da questo punto) i valori dei contatori di una mappa riuscita e riducono l'attività e li restituiscono al codice utente al termine dell'operazione MapReduce. C'è anche un valore attuale del contatore disponibile nello stato principale, quindi un essere umano che osserva il processo può tenere traccia di come si sta comportando.

Bene, immagino con tutti i concetti sopra, Hadoop sarà un gioco da ragazzi per te. In caso di domande sull'articolo MapReduce originale o su qualsiasi elemento correlato, per favore fatemelo sapere.


4

Non voglio sembrare banale, ma questo mi ha aiutato così tanto ed è piuttosto semplice:

cat input | map | reduce > output

4

Se hai familiarità con Python, la seguente è la spiegazione più semplice possibile di 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

Scopri come ogni segmento di dati non elaborati è stato elaborato singolarmente, in questo caso, moltiplicato per 2 (la parte della mappa di MapReduce). Sulla base di mapped_result, abbiamo concluso che il risultato sarebbe 42(la parte ridotta di MapReduce).

Una conclusione importante di questo esempio è il fatto che ogni blocco di elaborazione non dipende da un altro blocco. Ad esempio, se le thread_1mappe [1, 2, 3]e le thread_2mappe [4, 5, 6], il risultato finale di entrambi i thread sarebbe ancora, [2, 4, 6, 8, 10, 12]ma per questo abbiamo dimezzato il tempo di elaborazione. Lo stesso si può dire per l'operazione di riduzione ed è l'essenza di come MapReduce funziona nel calcolo parallelo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.