Explicação simples do MapReduce?


166

Relacionado à minha pergunta do CouchDB .

Alguém pode explicar o MapReduce em termos que um numbnuts possa entender?



3
E aqui está a minha opinião: MapReduce para e com as crianças .
22760 Michael Jackson

@ MichaelHausenblas - Adoro o seu exemplo: fácil de entender e divertido para toda a família.
Lee

Joel Spolsky tem uma boa explicação para iniciantes - joelonsoftware.com/items/2006/08/01.html
user2314737

Respostas:


187

Indo até o básico para Map e Reduce.


Map é uma função que "transforma" itens de algum tipo de lista em outro tipo de item e os coloca novamente no mesmo tipo de lista.

suponha que eu tenha uma lista de números: [1,2,3] e quero dobrar todos os números; nesse caso, a função "dobrar todos os números" é a função x = x * 2. E sem os mapeamentos, eu poderia escrever um loop simples, digamos

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

e eu teria A = [2, 4, 6] mas, em vez de escrever loops, se eu tivesse uma função de mapa, poderia escrever

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

x => x * 2 é uma função a ser executada contra os elementos em [1,2,3]. O que acontece é que o programa pega cada item, executa (x => x * 2) fazendo x igual a cada item e produz uma lista dos resultados.

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

então, após executar a função de mapa com (x => x * 2), você terá [2, 4, 6].


Reduzir é uma função que "coleta" os itens nas listas e executa alguns cálculos em todos eles, reduzindo-os a um único valor.

Encontrar uma soma ou encontrar médias são todas instâncias de uma função de redução. Como se você tivesse uma lista de números, digamos [7, 8, 9] e você os quisesse resumidos, você escreveria um loop como este

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

Mas, se você tiver acesso a uma função de redução, poderá escrevê-la assim

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

Agora é um pouco confuso por que existem 2 argumentos (0 e a função com xey) passados. Para que uma função de redução seja útil, ela deve ser capaz de pegar 2 itens, calcular algo e "reduzir" esses 2 itens para apenas um valor único; assim, o programa pode reduzir cada par até que tenhamos um valor único.

a execução seria a seguinte:

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

Mas você não deseja começar com zeros o tempo todo, portanto, o primeiro argumento existe para permitir que você especifique um valor inicial especificamente o valor da primeira result =linha.

digamos que você queira somar 2 listas, pode ser assim:

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 )

ou uma versão que você provavelmente encontraria no 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 )

É bom em um software de banco de dados porque, com o suporte Map \ Reduce, você pode trabalhar com o banco de dados sem precisar saber como os dados são armazenados em um banco de dados para usá-lo, é para isso que serve um mecanismo de banco de dados.

Você só precisa "dizer" ao mecanismo o que deseja, fornecendo a eles uma função Map ou Reduce e, em seguida, o mecanismo DB pode encontrar o caminho dos dados, aplicar sua função e apresentar os resultados que deseja. quero tudo sem que você saiba como ele percorre todos os registros.

Existem índices e chaves, junções e exibições, e muitos itens que um único banco de dados pode conter; assim, protegendo você contra como os dados são realmente armazenados, seu código fica mais fácil de escrever e manter.

O mesmo vale para a programação paralela, se você especificar apenas o que deseja fazer com os dados, em vez de realmente implementar o código de loop, a infraestrutura subjacente poderá "paralelizar" e executar sua função em um loop paralelo simultâneo para você.


Ok, eu entendo o mapa e reduzo tomadas individualmente. Mas que aplicações eu poderia ter da redução? Em um cenário do Google, eles o usariam, por exemplo, para somar uma série de parâmetros que fornecem a classificação de uma página para uma determinada palavra-chave?
Lorenzo

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

@lbolognini Existem muitos usos quando você abstrai o próprio conceito de loop. No cenário do Google, eles provavelmente têm milhares de máquinas para calcular pageranks, links e outros enfeites. O que eles fazem quando precisam adicionar mais alguns servidores? Modificar todos os códigos de loop provavelmente não é uma opção. Então, o que eles fizeram foi escrever o código de cálculo em uma função "Reduzir" ... E quando a lista de servidores muda, apenas a função "Reduzir" precisa ser alterada. Entendi?
chakrit

como reduziria a computação a média? Pelo que vejo, acho que você não conseguiu? talvez mapeie o numerador e o denominador e divida no final da soma de ambos?
andyczerwonka

@arcticpenguin Estou sendo um pouco genérico por lá. Na verdade, Average()é supostamente cereja em cima de Sum(). Mas eu falei sobre isso para ilustrar por que a função é chamada "Reduzir" ... Uma função média é algo que pega uma lista de números e a reduz a um único número (que é a média).
usar o seguinte comando

60

O MapReduce é um método para processar grandes somas de dados em paralelo sem exigir que o desenvolvedor grave qualquer outro código que não seja o mapeador e reduza as funções.

A função de mapa recebe dados e produz um resultado, que é mantido em uma barreira. Esta função pode ser executada em paralelo com um grande número da mesma tarefa de mapa . O conjunto de dados pode ser reduzido a um valor escalar.

Então, se você pensar nisso como uma instrução SQL

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

Podemos usar o map para obter nosso subconjunto de funcionários com salário> 1000, que mapeia para a barreira em baldes de tamanho de grupo.

Reduzir somará cada um desses grupos. Fornecendo seu conjunto de resultados.

apenas arrancou isso das minhas notas de estudo da universidade do papel do Google


33
  1. Pegue um monte de dados
  2. Realize algum tipo de transformação que converte todos os dados em outro tipo de dado
  3. Combine esses novos dados em dados ainda mais simples

O passo 2 é o mapa. Etapa 3 é reduzir.

Por exemplo,

  1. Obtenha tempo entre dois impulsos em um par de medidores de pressão na estrada
  2. Mapeie esses tempos em velocidades com base na distância dos medidores
  3. Reduza essas velocidades para uma velocidade média

A razão pela qual o MapReduce é dividido entre Map e Reduce é porque diferentes partes podem ser facilmente executadas em paralelo. (Especialmente se o Reduce tiver certas propriedades matemáticas.)

Para uma descrição complexa, mas boa, do MapReduce, consulte: Modelo de programação do Google MapReduce - revisitado (PDF) .


1
Eu diria para a etapa 3, "combinar" em vez de "transformar"
TraumaPony

Primeira vez, três respostas combinadas são a melhor resposta. Leia primeiro o link do artigo de Nasser (nível teórico hi). Em seguida, a resposta do chakrit (explicação individual do mapa-reduzir) Agora a resposta do Frank (qual é o famoso idioma do MapReduce.) Obrigado a vocês três. :)
Ajeet Ganga

20

MAP e REDUCE são funções antigas do Lisp de uma época em que o homem matou os últimos dinossauros.

Imagine que você tenha uma lista de cidades com informações sobre o nome, número de pessoas que moram lá e o tamanho da cidade:

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

Agora você pode querer encontrar a cidade com a maior densidade populacional.

Primeiro, criamos uma lista de nomes de cidades e densidade populacional usando o 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 REDUCE, agora podemos encontrar a cidade com a maior densidade populacional.

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

 =>   (C 5000/7)

Combinando as duas partes, obtemos o seguinte 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 introduzir funções:

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

Em seguida, podemos escrever nosso código MAP REDUCE como:

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

 =>   (C 5000/7)

Ele chama MAPe REDUCE(avaliação é de dentro para fora), por isso é chamado de redução de mapa .


@MoMolog: a função MAX já existe e faz algo ligeiramente diferente. Além disso: não se deve redefinir o MAX.
Rainer Joswig

max-densitycompara o segundo elemento dos argumentos passados, certo? Desculpe pela edição boba.
quer

@MoMolog: sim, é o segundo elemento e só é útil no contexto deste pequeno exemplo. O código também é de propósito escrito no velho estilo ligeiramente Lisp com listas como estruturas de dados ...
Rainer Joswig

17

Vamos pegar o exemplo do artigo do Google . O objetivo do MapReduce é poder usar com eficiência uma carga de unidades de processamento trabalhando paralelamente para algum tipo de algoritmo. O exemplo é o seguinte: você deseja extrair todas as palavras e sua contagem em um conjunto de documentos.

Implementação 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

Implementação do 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

Em torno disso, você terá um programa mestre que particionará o conjunto de documentos em "divisões", que serão tratados em paralelo na fase Mapa. Os valores emitidos são gravados pelo trabalhador em um buffer específico para o trabalhador. O programa mestre então delega outros trabalhadores para executar a fase Reduzir assim que for notificado que o buffer está pronto para ser manipulado.

Cada saída de trabalhador (sendo um trabalhador de Mapa ou Redução) é de fato um arquivo armazenado no sistema de arquivos distribuídos (GFS para Google) ou no banco de dados distribuído do CouchDB.


10

Uma introdução realmente fácil , rápida e "para manequins" ao MapReduce está disponível em: http://www.marcolotz.com/?p=67

Publicando parte do conteúdo:

Primeiro de tudo, por que o MapReduce foi criado originalmente?

Basicamente, o Google precisava de uma solução para tornar grandes tarefas de computação facilmente paralelizáveis, permitindo que os dados fossem distribuídos em várias máquinas conectadas através de uma rede. Além disso, tinha que lidar com a falha da máquina de maneira transparente e gerenciar problemas de balanceamento de carga.

Quais são os verdadeiros pontos fortes do MapReduce?

Pode-se dizer que a mágica do MapReduce se baseia no aplicativo de funções Map e Reduce. Devo confessar companheiro, que discordo totalmente. O principal recurso que tornou o MapReduce tão popular é sua capacidade de paralelização e distribuição automáticas, combinadas com a interface simples. Esses fatores foram somados com o tratamento transparente de falhas para a maioria dos erros que tornaram essa estrutura tão popular.

Um pouco mais de profundidade no papel:

O MapReduce foi mencionado originalmente em um artigo do Google (Dean & Ghemawat, 2004 - link aqui) como uma solução para fazer cálculos no Big Data usando uma abordagem paralela e clusters de computadores de mercadorias. Ao contrário do Hadoop, que é escrito em Java, a estrutura do Google é escrita em C ++. O documento descreve como uma estrutura paralela se comportaria usando as funções Map e Reduce da programação funcional em grandes conjuntos de dados.

Nesta solução, haveria duas etapas principais - denominadas Mapear e Reduzir -, com uma etapa opcional entre a primeira e a segunda - chamada Combinar. A etapa Mapa seria executada primeiro, executaria cálculos no par de valores-chave de entrada e geraria um novo valor-chave de saída. É preciso ter em mente que o formato dos pares de valores-chave de entrada não precisa necessariamente corresponder ao par de formatos de saída. A etapa Reduzir reuniria todos os valores da mesma chave, executando outros cálculos sobre ela. Como resultado, essa última etapa produziria pares de valores-chave. Uma das aplicações mais triviais do MapReduce é implementar a contagem de palavras.

O pseudocódigo para esta aplicação é apresentado abaixo:

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 pode notar, o mapa lê todas as palavras em um registro (nesse caso, um registro pode ser uma linha) e emite a palavra como chave e o número 1 como valor. Posteriormente, a redução agrupará todos os valores da mesma chave. Vamos dar um exemplo: imagine que a palavra 'casa' apareça três vezes no registro. A entrada do redutor seria [house, [1,1,1]]. No redutor, somará todos os valores da casa-chave e fornecerá como saída o seguinte valor-chave: [casa, [3]].

Aqui está uma imagem de como isso seria em uma estrutura MapReduce:

Imagem do documento original do Google MapReduce

Como alguns outros exemplos clássicos de aplicativos MapReduce, pode-se dizer:

• Contagem da frequência de acesso à URL

• Gráfico reverso de links da Web

• Grep distribuído

• Vetor de termos por host

Para evitar muito tráfego de rede, o documento descreve como a estrutura deve tentar manter a localidade dos dados. Isso significa que ele deve sempre tentar garantir que uma máquina executando tarefas de mapa tenha os dados em sua memória / armazenamento local, evitando buscá-los na rede. Com o objetivo de reduzir a rede através da colocação de um mapeador, é usada a etapa combinadora opcional, descrita anteriormente. O Combiner executa cálculos na saída dos mapeadores em uma determinada máquina antes de enviá-los aos Redutores - que podem estar em outra máquina.

O documento também descreve como os elementos da estrutura devem se comportar em caso de falhas. Esses elementos, no documento, são chamados de trabalhador e mestre. Eles serão divididos em elementos mais específicos nas implementações de código aberto. Como o Google apenas descreveu a abordagem no documento e não lançou seu software proprietário, muitas estruturas de código aberto foram criadas para implementar o modelo. Como exemplos, pode-se dizer o Hadoop ou o recurso limitado do MapReduce no MongoDB.

O tempo de execução deve cuidar de detalhes de programadores não especialistas, como particionar os dados de entrada, agendar a execução do programa em um grande conjunto de máquinas, lidar com falhas de máquinas (de maneira transparente, é claro) e gerenciar a comunicação entre máquinas . Um usuário experiente pode ajustar esses parâmetros, como os dados de entrada serão particionados entre os trabalhadores.

Conceitos chave:

Tolerância a falhas:Ele deve tolerar a falha da máquina normalmente. Para fazer isso, o mestre envia um ping aos trabalhadores periodicamente. Se o mestre não receber respostas de um determinado trabalhador em um lapso de tempo definido, o mestre definirá o trabalho como falhado nesse trabalhador. Nesse caso, todas as tarefas de mapa concluídas pelo trabalhador com defeito são jogadas fora e são entregues a outro trabalhador disponível. O mesmo acontece se o trabalhador ainda estiver processando um mapa ou uma tarefa de redução. Observe que, se o trabalhador já concluiu sua parte de redução, todo o cálculo já foi concluído no momento em que falhou e não precisa ser redefinido. Como principal ponto de falha, se o mestre falhar, todo o trabalho falhará. Por esse motivo, é possível definir pontos de verificação periódicos para o mestre, a fim de salvar sua estrutura de dados.

Localidade: para evitar o tráfego de rede, a estrutura tenta garantir que todos os dados de entrada estejam disponíveis localmente para as máquinas que irão realizar cálculos nelas. Na descrição original, ele usa o Google File System (GFS) com fator de replicação definido como 3 e tamanhos de bloco de 64 MB. Isso significa que o mesmo bloco de 64 MB (que compõe um arquivo no sistema de arquivos) terá cópias idênticas em três máquinas diferentes. O mestre sabe onde estão os blocos e tenta agendar trabalhos de mapa nessa máquina. Se isso falhar, o mestre tenta alocar uma máquina perto de uma réplica dos dados de entrada das tarefas (ou seja, uma máquina operadora no mesmo rack da máquina de dados).

Granularidade da tarefa: Supondo que cada fase do mapa seja dividida em partes M e que cada fase de Redução seja dividida em partes R, o ideal seria que M e R fossem muito maiores que o número de máquinas operadoras. Isso se deve ao fato de um trabalhador que executa muitas tarefas diferentes melhorar o balanceamento de carga dinâmico. Além disso, aumenta a velocidade de recuperação em caso de falha do trabalhador (uma vez que as muitas tarefas de mapeamento concluídas podem ser espalhadas por todas as outras máquinas).

Tarefas de backup: às vezes, um trabalhador de mapa ou redutor pode se comportar muito mais devagar do que os outros no cluster. Isso pode manter o tempo total de processamento e torná-lo igual ao tempo de processamento dessa única máquina lenta. O documento original descreve uma alternativa chamada Tarefas de Backup, que é agendada pelo mestre quando uma operação do MapReduce está quase concluída. Essas são tarefas agendadas pelo mestre das tarefas em andamento. Portanto, a operação MapReduce é concluída quando o primário ou o backup termina.

Contadores: Às vezes, pode-se desejar contar ocorrências de eventos. Por esse motivo, conta onde foi criado. Os valores dos contadores em cada trabalhador são propagados periodicamente para o mestre. O mestre então agrega (Sim. Parece que os agregadores Pregel vieram deste local) os valores dos contadores de um mapa bem-sucedido, reduzem a tarefa e os devolvem ao código do usuário quando a operação MapReduce estiver concluída. Há também um valor atual de contador disponível no status mestre, para que um humano que esteja assistindo o processo possa acompanhar como está se comportando.

Bem, acho que com todos os conceitos acima, o Hadoop será um pedaço de bolo para você. Se você tiver alguma dúvida sobre o artigo original do MapReduce ou algo relacionado, entre em contato.


4

Não quero parecer banal, mas isso me ajudou muito e é bem simples:

cat input | map | reduce > output

4

Se você está familiarizado com o Python, a seguir é apresentada a explicação mais simples possível do 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

Veja como cada segmento de dados brutos foi processado individualmente, neste caso, multiplicado por 2 (a parte do mapa do MapReduce). Com base na mapped_result, concluímos que o resultado seria 42(a reduzir parte da MapReduce).

Uma conclusão importante desse exemplo é o fato de que cada parte do processamento não depende de outra parte. Por exemplo, se thread_1mapas [1, 2, 3]e thread_2mapas [4, 5, 6], o resultado final de ambos os segmentos ainda seria, [2, 4, 6, 8, 10, 12]mas reduzimos pela metade o tempo de processamento para isso. O mesmo pode ser dito para a operação de redução e é a essência de como o MapReduce funciona na computação paralela.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.