Просте пояснення MapReduce?


166

Пов'язане з моїм запитанням CouchDB .

Чи може хтось пояснити MapReduce в термінах, який чисел може зрозуміти?




@MichaelHausenblas - Я люблю твій приклад: легко зрозуміти і розважитися для всієї родини.
Лі

Joel Spolsky має хороше пояснення для початківців - joelonsoftware.com/items/2006/08/01.html
user2314737

Відповіді:


187

Переходимо повністю до основ для карт та скорочення.


Карта - це функція, яка "перетворює" елементи в якомусь списку в інший тип елементів і повертає їх назад у такий самий список.

припустимо, у мене є список чисел: [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  

тож після виконання функції карти з (x => x * 2) у вас буде [2, 4, 6].


Зменшити - це функція, яка "збирає" елементи в списках і виконує деякі обчислення всіх , таким чином зменшуючи їх до єдиного значення.

Пошук суми або знаходження середніх значень - це всі випадки зменшення функції. Наприклад, якщо у вас є список чисел, скажімо [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 ви можете працювати з базою даних, не знаючи, як зберігаються дані в БД, щоб їх використовувати, ось для чого призначений двигун БД.

Вам просто потрібно мати можливість "сказати" двигуну, що ви хочете, надавши їм або карту, або функцію зменшення, і тоді двигун БД міг знайти свої дані навколо даних, застосувати свою функцію та придумати результати, які ви хотіти всіх, не знаючи, як воно перетинає всі записи.

Є індекси та ключі, приєднання та перегляди та багато матеріалів, які може вмістити в одній базі даних, тож захищаючи вас від того, як фактично зберігаються дані, ваш код спрощується писати та підтримувати.

Те ж саме стосується і паралельного програмування, якщо ви вказуєте лише те, що ви хочете зробити з даними, а не реалізувати циклічний код, тоді базова інфраструктура може "паралелізувати" і виконати вашу функцію в одночасному паралельному циклі.


Гаразд, я розумію карту і зменшую окремо. Але які додатки я міг би мати зменшення? Чи в сценарії Google вони використовуватимуть його, наприклад, для підсумовування ряду параметрів, які дають їм рейтинг сторінки за заданим ключовим словом?
Лоренцо

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

@lbolognini Існує багато застосувань, коли ви абстрагуєтесь від самої концепції циклічного циклу. У сценарії Google, ймовірно, є 1000 машин для обчислення сторінок, посилань та іншого. Що вони роблять, коли їм потрібно додати ще кілька серверів? Змінення кожного певного коду, певно, не є варіантом. Тож, що вони зробили, це те, що вони записують свій код обчислення замість функції "Зменшити" замість цього ... І коли список серверів змінюється, потрібно змінити лише функцію "Зменшити". Зрозумів?
чакрит

як би зменшити обчислення середнього? з того, що я бачу, я здогадуюсь, що ти не зміг? може, зіставити чисельник і знаменник і розділити в кінці підсумовування обох?
andyczerwonka

@arcticpenguin Я трохи надто загальний. Насправді Average()це нібито обмерзання зверху Sum(). Але я говорив про це, щоб проілюструвати, чому функція називається "Зменшити" ... Середня функція - це те, що приймає список чисел і зводить її до єдиного числа (що є середнім).
чакрит

60

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

Функція карти приймає дані і викреслює результат, який утримується в бар'єрі. Ця функція може працювати паралельно великій кількості одного і того ж завдання з картою . Потім набір даних може бути зменшений до скалярного значення.

Тож якщо ви думаєте про це як про оператор 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.) Завдяки вам троє. :)
Ajeet Ganga

20

КАРТА та ЗНИЖЕННЯ - це старі функції Ліспа з часів, коли людина вбивала останніх динозаврів.

Уявіть, що у вас є список міст із інформацією про назву, кількість людей, які там проживають, та розмір міста:

(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 (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.
Rainer Joswig

max-densityпорівнює другий елемент переданих аргументів, правда? Вибачте за безглузду редакцію.
Олександр Пресбер

@MoMolog: так, це другий елемент, і це корисно лише в контексті цього невеликого прикладу. Код також цілеспрямовано написаний у трохи старому стилі Lisp із списками як структури даних ...
Rainer Joswig

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

Навколо цього ви матимете головну програму, яка розділить набір документів на "розщеплення", які будуть оброблятися паралельно на етапі "Карта". Виправлені значення записуються працівником у буфер, специфічний для працівника. Потім головна програма делегує інших працівників для виконання етапу зменшення, як тільки буде повідомлено, що буфер готовий до обробки.

Кожен вихідний працівник (будучи картою або скороченням) - це фактично файл, що зберігається в розподіленій файловій системі (GFS для Google) або в розподіленій базі даних для CouchDB.


10

По-справжньому просте , швидке та "для муляжів" вступ у MapReduce доступне за посиланням: http://www.marcolotz.com/?p=67

Опублікувавши частину його вмісту:

Перш за все, чому був створений MapReduce спочатку?

В основному Google потребував рішення для того, щоб зробити великі обчислювальні завдання легко паралельними, що дозволило розподіляти дані в декількох машинах, підключених через мережу. Окрім цього, воно мало прозоро обробляти несправності машини та керувати проблемами балансування навантаження.

Які справжні сильні сторони MapReduce?

Можна сказати, що магія MapReduce заснована на застосуванні функцій Map and Reduce. Я мушу зізнатися, товариш, що я категорично не згоден. Основна особливість, яка зробила MapReduce настільки популярним, - це його можливість автоматичної паралелізації та розподілу в поєднанні з простим інтерфейсом. Ці фактори, підсумовані прозорими помилками для більшості помилок, зробили цю основу такою популярною.

Трохи більше глибини на папері:

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

У цьому рішенні було б два основних кроки - під назвою "Карта" та "Зменшити", з необов'язковим кроком між першим та другим - під назвою "Комбінувати". Етап Map буде запущений спочатку, зробіть обчислення у вхідній парі ключ-значення та генеруйте новий вихідний ключ-значення. Потрібно мати на увазі, що формат пар вхідних ключів-значень не обов'язково повинен відповідати парі вихідного формату. Крок зменшення збирає всі значення одного ключа, виконуючи інші обчислення над ним. В результаті цього останнього кроку виведе пари ключ-значення. Одне з найбільш тривіальних застосувань 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-адреси

• Зворотний графік веб-посилань

• розподілений греп

• Термін вектор на хоста

Щоб уникнути занадто великої кількості мережевого трафіку, у статті описано, як фреймворк повинен намагатися підтримувати локальність даних. Це означає, що він повинен завжди намагатися переконатися, що машина, яка виконує завдання Map, має дані в своїй пам'яті / локальному сховищі, уникаючи їх отримання з мережі. З метою зменшення мережі за допомогою розміщення картографа використовується необов'язковий крок комбінатора, описаний раніше. Комбінатор виконує обчислення на виході відображувачів у певній машині, перш ніж надсилати їх на редуктори - які можуть бути в іншій машині.

У документі також описано, як повинні діяти елементи рамки у разі несправностей. Ці елементи в роботі називаються робітником і майстром. Вони будуть розділені на більш конкретні елементи в реалізаціях з відкритим кодом. Оскільки Google лише описав цей підхід у роботі та не випустив власне програмне забезпечення, для реалізації моделі було створено багато структур з відкритим кодом. Як приклад можна навести Hadoop або обмежену функцію MapReduce в MongoDB.

Пробіг повинен дбати про непрофесійні деталі програмістів, як розподіл вхідних даних, планування виконання програми у великому наборі машин, поводження з відмовами машин (зрозуміло, прозорим способом) та управління міжмашинним зв’язком . Досвідчений користувач може налаштувати ці параметри як спосіб розподілу вхідних даних між працівниками.

Основні поняття:

Помилка допуску:Він повинен витримати несправність машини витончено. Для цього майстер періодично обманює працівників. Якщо майстер не отримає відповідей від певного працівника через певний проміжок часу, він визначить роботу як не виконану в цьому працівнику. У цьому випадку всі завдання карт, виконані несправним працівником, викидаються та передаються іншому доступному працівникові. Подібне трапляється, якщо працівник ще обробляв карту або зменшує завдання. Зауважте, що якщо працівник вже завершив свою зменшену частину, усі обчислення були вже закінчені до моменту його відмови і не потрібно його скидати. Як головний момент відмови, якщо майстер виходить з ладу, все завдання виходить з ладу. З цієї причини можна визначити періодичні контрольні точки для ведучого, щоб зберегти його структуру даних.

Місцевість: Щоб уникнути мережевого трафіку, система намагається переконатися, що всі вхідні дані локально доступні для машин, які збираються виконувати обчислення на них. У первинному описі використовується файлова система Google (GFS) з коефіцієнтом реплікації, встановленою на 3, та розмірами блоків 64 Мб. Це означає, що той самий блок об'ємом 64 Мб (який створює файл у файловій системі) матиме однакові копії на трьох різних машинах. Майстер знає, де є блоки, і намагається запланувати завдання на карті в цій машині. Якщо це не вдається, майстер намагається виділити машину біля репліки вхідних даних завдань (тобто робочу машину в одній стійці машини даних).

Гранульованість завдань: якщо припустити, що кожна фаза карти поділена на M шматочки і що кожна фаза зменшення розділена на R частини, ідеальним було б, щоб M і R були набагато більшими, ніж кількість працюючих машин. Це пов’язано з тим, що працівник, виконуючи багато різних завдань, покращує динамічне врівноваження навантаження. Крім цього, вона збільшує швидкість відновлення у разі відмови працівника (оскільки багато завдань, які він виконав, можна розкласти по всіх інших машинах).

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

Лічильники: Іноді можна порахувати події подій. З цієї причини підраховується, де створено. Лічильники лічильників у кожного працівника періодично передаються майстру. Потім майстер агрегує (так. Схоже, агрегатори Прегеля прийшли з цього місця) лічильники успішної карти та зменшують завдання та повертають їх до коду користувача, коли операція 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 працює в паралельних обчисленнях.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.