Простое объяснение MapReduce?

166

Связанный с моим вопросом CouchDB .

Может ли кто-нибудь объяснить MapReduce в терминах, которые могут понять нибунты?

reefnet_alex
источник
3
И вот мое мнение : MapReduce для детей и с детьми .
Майкл Хаузенблас
@MichaelHausenblas - я люблю твой пример: легко понять и весело для всей семьи.
Ли
У Джоэла Спольски есть хорошее объяснение для начинающих - joelonsoftware.com/items/2006/08/01.html
user2314737

Ответы:

187

Переходя к основам Map и Reduce.


Карта - это функция, которая «преобразует» элементы в каком-либо списке в другой тип и помещает их обратно в тот же список.

Предположим, у меня есть список чисел: [1,2,3], и я хочу удваивать каждое число, в этом случае функция «удваивать каждое число» является функцией x = x * 2. И без отображений я мог бы написать простой цикл, скажем

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

и я бы имел A = [2, 4, 6], но вместо записи циклов, если бы у меня была функция карты, я мог бы написать

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

x => x * 2 - это функция, которая должна выполняться для элементов из [1,2,3]. То, что происходит, - то, что программа берет каждый элемент, выполняет (x => x * 2) против него, делая x равным каждому элементу, и производит список результатов.

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

поэтому после выполнения функции map с помощью (x => x * 2) у вас будет [2, 4, 6].


Reduce - это функция, которая «собирает» элементы в списках и выполняет некоторые вычисления для всех из них, сводя их к одному значению.

Поиск суммы или нахождение средних значений - все это примеры функции снижения. Например, если у вас есть список чисел, скажем, [7, 8, 9], и вы хотите, чтобы они суммировались, вы бы написали такой цикл

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

Но если у вас есть доступ к функции приведения, вы могли бы написать это так

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

Теперь немного сбивает с толку, почему передано 2 аргумента (0 и функция с x и y). Чтобы функция сокращения была полезной, она должна иметь возможность брать 2 элемента, вычислять что-то и «сокращать» эти 2 элемента до одного единственного значения, таким образом, программа может уменьшить каждую пару до тех пор, пока мы не получим одно значение.

выполнение будет следующим:

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

Но вы не хотите начинать с нуля все время, поэтому существует первый аргумент, позволяющий вам указать начальное значение, а именно значение в первой result =строке.

скажем, вы хотите сложить 2 списка, это может выглядеть так:

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 )

или версию, которую вы с большей вероятностью найдете в реальном мире:

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

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

Это хорошая вещь в программном обеспечении БД, потому что с поддержкой Map \ Reduce вы можете работать с базой данных без необходимости знать, как данные хранятся в БД для ее использования, вот для чего предназначен механизм БД.

Вам просто нужно уметь «подсказать» движку, что вы хотите, предоставив ему функцию Map или Reduce, и тогда движок БД сможет обойти данные, применить вашу функцию и получить результаты, которые вы хочу, чтобы все не знали, как это перебирает все записи.

Существуют индексы, ключи, объединения и представления, а также множество вещей, которые может содержать одна база данных, поэтому, защищая вас от того, как на самом деле хранятся данные, ваш код становится проще для написания и обслуживания.

То же самое относится и к параллельному программированию: если вы указываете только то, что вы хотите делать с данными, вместо того, чтобы фактически реализовывать циклический код, тогда базовая инфраструктура может «распараллелить» и выполнить вашу функцию в параллельном параллельном цикле для вас.

chakrit
источник
Хорошо, я понимаю карту и снимаю отдельно взятый. Но какие приложения можно использовать для уменьшения? В сценарии Google они будут использовать его, например, для суммирования ряда параметров, которые дают им рейтинг страницы по заданному ключевому слову?
Лоренцо
@lbolognini var total = orderes.Sum (o => o.UnitPrice * o.Quantity)
чакрит
@lbolognini Существует много вариантов использования абстрагирования от самой концепции зацикливания. В сценарии Google у них, вероятно, есть тысячи машин для вычисления постраничных рангов, ссылок и еще много чего. Что они делают, когда им нужно добавить еще несколько серверов? Модификация каждого кода зацикливания, вероятно, не вариант. Так что они сделали то, что вместо этого они написали свой код вычисления для функции «Уменьшить» ... И когда список серверов изменяется, необходимо изменить только функцию «Уменьшить». Понял?
чакрит
Как бы уменьшить вычисление среднего? из того, что я вижу, я думаю, ты не мог? Может быть, сопоставить числитель и знаменатель и разделить в конце суммирования обоих?
andyczerwonka
@arcticpenguin Я немного слишком универсален. На самом деле Average()якобы глазурь сверху Sum(). Но я говорил об этом, чтобы проиллюстрировать, почему функция называется «Уменьшить» ... Средняя функция - это то, что берет список чисел и сводит его к одному числу (которое является средним).
чакрит
60

MapReduce - это метод параллельной обработки огромных объемов данных, не требующий от разработчика написания любого другого кода, кроме mapper, и сокращения функций.

Функция карты принимает данные и производит результат, который содержится в барьере. Эта функция может работать параллельно с большим количеством одной и той же задачи карты . Набор данных затем может быть уменьшен до скалярного значения.

Так что, если вы думаете об этом как о выражении SQL

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

Мы можем использовать карту, чтобы получить подмножество сотрудников с зарплатой> 1000, которые карта выбрасывает в барьер в группы размеров группы.

Сокращение будет суммировать каждую из этих групп. Дать вам ваш набор результатов.

только что вытащил это из моих заметок об исследовании университета Google бумаги

Джонно Нолан
источник
33
  1. Взять кучу данных
  2. Выполните какое-то преобразование, которое преобразует каждый элемент данных в другой тип данных.
  3. Объедините эти новые данные в еще более простые данные

Шаг 2 - Карта. Шаг 3 - Уменьшить.

Например,

  1. Получить время между двумя импульсами на пару измерителей давления на дороге
  2. Составьте карту этих времен в скоростях, основанных на расстоянии метров
  3. Уменьшите эти скорости до средней скорости

Причина, по которой MapReduce разделена между Map и Reduce, заключается в том, что разные части могут быть легко выполнены параллельно. (Особенно, если Reduce имеет определенные математические свойства.)

Сложное, но хорошее описание MapReduce см .: Модель программирования Google MapReduce - вновь (PDF) .

Фрэнк Крюгер
источник
1
Я бы сказал для шага 3 «объединить» вместо «преобразовать»
TraumaPony
В первый раз три ответа вместе - ЛУЧШИЙ ответ. Сначала прочитайте ссылку на статью Насера ​​(теоретический высокий уровень). Затем ответ Чакрита (индивидуальное объяснение сокращения карты). Теперь ответ Фрэнка (Что такое знаменитый идиома MapReduce.) Спасибо вам всем. :)
Аджит Ганга
20

MAP и REDUCE - старые функции Lisp с того времени, когда человек убил последних динозавров.

Представьте, что у вас есть список городов с информацией о названии, количестве людей, проживающих там, и размере города:

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

Теперь вы можете найти город с самой высокой плотностью населения.

Сначала мы создаем список названий городов и плотности населения, используя 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))

Используя REDUCE, мы можем теперь найти город с самой большой плотностью населения.

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

 =>   (C 5000/7)

Объединяя обе части, мы получаем следующий код:

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

Давайте введем функции:

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

Тогда мы можем написать наш код MAP REDUCE как:

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

 =>   (C 5000/7)

Он вызывает MAPи REDUCE(оценка наизнанку), поэтому он называется уменьшением карты .

Райнер Йосвиг
источник
@MoMolog: функция MAX уже существует и делает что-то немного другое. Кроме того: не следует переопределять MAX.
Райнер Йосвиг,
max-densityсравнивает второй элемент переданных аргументов, верно? Извините за глупое редактирование.
Александр Пресбер
@MoMolog: да, это второй элемент, и он полезен только в контексте этого небольшого примера. Код также специально написан на немного старомодном Лиспе со списками в качестве структур данных ...
Райнер Йосвиг,
17

Давайте возьмем пример из статьи Google . Цель MapReduce - иметь возможность эффективно использовать множество процессоров, работающих параллельно для каких-то алгоритмов. Примером является следующее: вы хотите извлечь все слова и их количество в набор документов.

Типичная реализация:

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

Реализация 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

Вокруг этого у вас будет мастер-программа, которая разделит набор документов на «сплиты», которые будут обрабатываться параллельно на этапе Map. Полученные значения записываются рабочим в буфер, определенный для рабочего. Затем главная программа делегирует другим работникам выполнение фазы сокращения, как только она получает уведомление о готовности буфера к обработке.

Каждый рабочий вывод (будучи рабочим Map или Reduce) на самом деле является файлом, хранящимся в распределенной файловой системе (GFS для Google) или в распределенной базе данных для CouchDB.

Дэмиен Б
источник
10

Очень простое , быстрое и "для чайников" введение в MapReduce доступно по адресу: http://www.marcolotz.com/?p=67.

Публикация части контента:

Прежде всего, почему изначально был создан MapReduce?

По сути, Google требовалось решение для простого распараллеливания больших вычислительных заданий, позволяющее распределять данные на нескольких компьютерах, подключенных через сеть. Кроме того, он должен был прозрачно обрабатывать сбой компьютера и управлять проблемами балансировки нагрузки.

Каковы сильные стороны MapReduce?

Можно сказать, что магия MapReduce основана на приложении функций Map и Reduce. Я должен признаться, приятель, что я категорически не согласен. Главная особенность, которая сделала MapReduce столь популярной, - это возможность автоматического распараллеливания и распределения в сочетании с простым интерфейсом. Эти факторы суммировались с прозрачной обработкой отказов для большинства ошибок, сделавших эту платформу настолько популярной.

Еще немного глубины на бумаге:

MapReduce был первоначально упомянут в статье Google (Dean & Ghemawat, 2004 - ссылка здесь) в качестве решения для выполнения вычислений в больших данных с использованием параллельного подхода и кластеров с товарными компьютерами. В отличие от Hadoop, написанного на Java, платформа Google написана на C ++. В документе описывается, как будет вести себя параллельная структура, используя функции Map и Reduce из функционального программирования для больших наборов данных.

В этом решении будет два основных шага - называемых Map и Reduce - с необязательным шагом между первым и вторым - называемых Combine. Сначала выполняется шаг Map, выполняются вычисления в паре ключ-значение ввода и генерируется новый ключ-значение вывода. Следует помнить, что формат пар «ключ-значение» не обязательно должен совпадать с парой выходного формата. Шаг Reduce собирает все значения одного и того же ключа, выполняя другие вычисления над ним. В результате этот последний шаг будет выводить пары ключ-значение. Одним из самых тривиальных приложений MapReduce является реализация подсчета слов.

Псевдокод для этого приложения приведен ниже:

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

Как можно заметить, карта читает все слова в записи (в данном случае запись может быть строкой) и выдает слово в качестве ключа, а число 1 - в качестве значения. Позже, при сокращении будут сгруппированы все значения одного и того же ключа. Давайте приведем пример: представьте, что слово «дом» встречается в записи три раза. Вход редуктора будет [дом, [1,1,1]]. В редукторе он суммирует все значения для ключевого дома и выдает на выходе следующее ключевое значение: [house, [3]].

Вот изображение того, как это будет выглядеть в среде MapReduce:

Изображение из оригинальной бумаги Google MapReduce

В качестве нескольких других классических примеров приложений MapReduce можно сказать:

• Подсчет частоты доступа к URL

• Обратный график веб-ссылки

• Распределенная Grep

• Термин Вектор на хост

Чтобы избежать слишком большого сетевого трафика, в документе описывается, как инфраструктура должна пытаться поддерживать локальность данных. Это означает, что он должен всегда пытаться убедиться, что машина, выполняющая задания Map, хранит данные в своей памяти / локальном хранилище, избегая их извлечения из сети. Стремясь уменьшить сеть за счет использования картографа, используется дополнительный шаг объединителя, описанный ранее. Combiner выполняет вычисления на выходе преобразователей в заданной машине перед отправкой их в редукторы, которые могут быть на другой машине.

В документе также описывается, как элементы платформы должны вести себя в случае неисправностей. Эти элементы в статье называются рабочими и хозяевами. Они будут разделены на более конкретные элементы в реализациях с открытым исходным кодом. Поскольку Google только описал подход в документе и не выпустил свое проприетарное программное обеспечение, было создано множество сред с открытым исходным кодом для реализации этой модели. В качестве примеров можно привести Hadoop или ограниченную функцию MapReduce в MongoDB.

Во время выполнения следует позаботиться о деталях неопытных программистов, таких как разделение входных данных, планирование выполнения программы на большом наборе машин, обработка сбоев машин (прозрачным способом, конечно) и управление межмашинным взаимодействием , Опытный пользователь может настроить эти параметры, например, как входные данные будут распределены между рабочими.

Ключевые идеи:

Отказоустойчивость:Он должен терпеть отказ машины изящно. Для этого мастер периодически пингует рабочих. Если мастер не получает ответы от данного работника за определенный промежуток времени, мастер определит работу как неудавшуюся в этом работнике. В этом случае все задачи карты, выполненные неисправным работником, отбрасываются и передаются другому доступному работнику. Подобное происходит, если работник все еще обрабатывает карту или задачу сокращения. Обратите внимание, что если рабочий уже выполнил свою часть сокращения, все вычисления были уже завершены к тому времени, когда произошел сбой, и их не нужно сбрасывать. В качестве основной точки отказа, если мастер выходит из строя, все задания не выполняются. По этой причине можно определить периодические контрольные точки для мастера, чтобы сохранить его структуру данных.

Локальность: во избежание сетевого трафика платформа пытается убедиться, что все входные данные локально доступны для машин, которые будут выполнять вычисления на них. В исходном описании используется файловая система Google (GFS) с коэффициентом репликации 3 и размером блоков 64 МБ. Это означает, что один и тот же блок размером 64 МБ (который составляет файл в файловой системе) будет иметь одинаковые копии на трех разных машинах. Мастер знает, где находятся блоки, и попытается составить расписание заданий карты на этом компьютере. Если это не удается, мастер пытается выделить компьютер рядом с репликой входных данных задач (т. Е. Рабочий компьютер в той же стойке компьютера данных).

Гранулярность задачи. Предполагая, что каждая фаза карты разделена на M частей, а каждая фаза уменьшения разделена на R частей, в идеале M и R должны быть намного больше, чем количество рабочих машин. Это связано с тем, что работник, выполняющий множество различных задач, улучшает динамическое распределение нагрузки. Кроме того, он увеличивает скорость восстановления в случае сбоя работника (поскольку многие выполненные карты могут быть распределены по всем остальным машинам).

Задачи резервного копирования: иногда рабочий Map или Reducer может работать намного медленнее, чем другие в кластере. Это может содержать общее время обработки и сделать его равным времени обработки той единственной медленной машины. В оригинальной статье описывается альтернатива, называемая задачами резервного копирования, которые планируются мастером, когда операция MapReduce близка к завершению. Это задачи, которые запланированы мастером текущих задач. Таким образом, операция MapReduce завершается, когда первичная или резервная копия завершается.

Счетчики: иногда может возникнуть необходимость подсчитать события. По этой причине считается, где создан. Значения счетчика у каждого работника периодически передаются мастеру. Затем мастер агрегирует (да. Похоже, агрегаторы Pregel пришли с этого места) значения счетчиков успешной карты и сокращают задачу и возвращают их в код пользователя после завершения операции MapReduce. Существует также текущее значение счетчика, доступное в основном состоянии, так что человек, наблюдающий за процессом, может отслеживать его поведение.

Ну, я думаю, со всеми вышеизложенными концепциями Hadoop станет для вас куском пирога. Если у вас есть какие-либо вопросы об оригинальной статье MapReduce или о чем-либо связанном, пожалуйста, дайте мне знать.

Прометей
источник
4

Я не хочу звучать банально, но это мне очень помогло, и все довольно просто:

cat input | map | reduce > output
Майк Дьюар
источник
4

Если вы знакомы с Python, ниже приведено простейшее объяснение 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

Посмотрите, как каждый сегмент необработанных данных обрабатывался индивидуально, в этом случае, умноженный на 2 ( часть карты MapReduce). Исходя из этого mapped_result, мы пришли к выводу, что результатом будет 42( уменьшенная часть MapReduce).

Важным выводом из этого примера является тот факт, что каждый блок обработки не зависит от другого блока. Например, если thread_1карты [1, 2, 3]и thread_2карты [4, 5, 6], конечный результат обоих потоков все равно будет, [2, 4, 6, 8, 10, 12]но мы вдвое сократили время обработки для этого. То же самое можно сказать и о операции сокращения, и в этом суть работы MapReduce в параллельных вычислениях.

Rafay
источник