Связанный с моим вопросом CouchDB .
Может ли кто-нибудь объяснить MapReduce в терминах, которые могут понять нибунты?
Связанный с моим вопросом CouchDB .
Может ли кто-нибудь объяснить MapReduce в терминах, которые могут понять нибунты?
Ответы:
Переходя к основам 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, и тогда движок БД сможет обойти данные, применить вашу функцию и получить результаты, которые вы хочу, чтобы все не знали, как это перебирает все записи.
Существуют индексы, ключи, объединения и представления, а также множество вещей, которые может содержать одна база данных, поэтому, защищая вас от того, как на самом деле хранятся данные, ваш код становится проще для написания и обслуживания.
То же самое относится и к параллельному программированию: если вы указываете только то, что вы хотите делать с данными, вместо того, чтобы фактически реализовывать циклический код, тогда базовая инфраструктура может «распараллелить» и выполнить вашу функцию в параллельном параллельном цикле для вас.
Average()
якобы глазурь сверху Sum()
. Но я говорил об этом, чтобы проиллюстрировать, почему функция называется «Уменьшить» ... Средняя функция - это то, что берет список чисел и сводит его к одному числу (которое является средним).
MapReduce - это метод параллельной обработки огромных объемов данных, не требующий от разработчика написания любого другого кода, кроме mapper, и сокращения функций.
Функция карты принимает данные и производит результат, который содержится в барьере. Эта функция может работать параллельно с большим количеством одной и той же задачи карты . Набор данных затем может быть уменьшен до скалярного значения.
Так что, если вы думаете об этом как о выражении SQL
SELECT SUM(salary)
FROM employees
WHERE salary > 1000
GROUP by deptname
Мы можем использовать карту, чтобы получить подмножество сотрудников с зарплатой> 1000, которые карта выбрасывает в барьер в группы размеров группы.
Сокращение будет суммировать каждую из этих групп. Дать вам ваш набор результатов.
только что вытащил это из моих заметок об исследовании университета Google бумаги
Шаг 2 - Карта. Шаг 3 - Уменьшить.
Например,
Причина, по которой MapReduce разделена между Map и Reduce, заключается в том, что разные части могут быть легко выполнены параллельно. (Особенно, если Reduce имеет определенные математические свойства.)
Сложное, но хорошее описание MapReduce см .: Модель программирования Google MapReduce - вновь (PDF) .
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
(оценка наизнанку), поэтому он называется уменьшением карты .
max-density
сравнивает второй элемент переданных аргументов, верно? Извините за глупое редактирование.
Давайте возьмем пример из статьи 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.
Очень простое , быстрое и "для чайников" введение в 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:
В качестве нескольких других классических примеров приложений 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 или о чем-либо связанном, пожалуйста, дайте мне знать.
Я не хочу звучать банально, но это мне очень помогло, и все довольно просто:
cat input | map | reduce > output
Если вы знакомы с 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 в параллельных вычислениях.