Як записи процесу Hadoop розбиваються на межі блоку?


119

Відповідно до Hadoop - The Definitive Guide

Логічні записи, які визначають FileInputFormats, зазвичай не вписуються акуратно в блоки HDFS. Наприклад, логічні записи TextInputFormat - це рядки, які частіше за все перетинають межі HDFS. Це не стосується функціонування вашої програми - наприклад, рядки не пропущені або порушені, але про це варто знати, оскільки це означає, що карти локальних даних (тобто карти, що працюють на тому ж хості, що і їх вхідні дані) виконає деякі віддалені зчитування. Незначні накладні витрати, які це викликає, як правило, не є значними.

Припустимо, рядок запису розділений на два блоки (b1 і b2). Маппер, що обробляє перший блок (b1), помітить, що останній рядок не має роздільника EOL і витягує решту рядка з наступного блоку даних (b2).

Як картограф, що обробляє другий блок (b2), визначає, що перший запис є неповним і повинен оброблятись, починаючи з другого запису в блоці (b2)?

Відповіді:


160

Цікаве запитання, я провів деякий час, переглядаючи код для деталей і ось мої думки. Клієнт обробляє розбиття InputFormat.getSplits, тому перегляд FileInputFormat дає таку інформацію:

  • Для кожного вхідного файлу отримайте довжину файлу, розмір блоку та обчисліть розмір розбиття так, max(minSize, min(maxSize, blockSize))де maxSizeвідповідає mapred.max.split.sizeі minSizeє mapred.min.split.size.
  • Розділіть файл на різні FileSplits на основі розміру розбиття, обчисленого вище. Тут важливо те, що кожен FileSplitініціалізується startпараметром, відповідним зміщенню у вхідному файлі . У цій точці досі немає обробки ліній. Відповідна частина коду виглядає так:

    while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
      int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
      splits.add(new FileSplit(path, length-bytesRemaining, splitSize, 
                               blkLocations[blkIndex].getHosts()));
      bytesRemaining -= splitSize;
    }
    

Після цього, якщо ви подивитеся на те, LineRecordReaderщо визначено значком TextInputFormat, саме там обробляються рядки:

  • Коли ви ініціалізуєте свій, LineRecordReaderвін намагається інстанціювати a, LineReaderщо є абстракцією, щоб мати можливість читати рядки FSDataInputStream. Є 2 випадки:
  • Якщо є CompressionCodecвизначений, то цей кодек відповідає за обробку меж. Напевно, не стосується вашого питання.
  • Якщо кодека немає, то тут цікаво: якщо startваш InputSplitінший ніж 0, ви відхилили 1 символ і пропустили перший рядок, який ви зіткнулися з ідентифікацією \ n або \ r \ n (Windows) ! Зворотний шлях важливий, тому що, якщо межі вашої лінії збігаються з розділеними межами, це гарантує, що ви не пропустите дійсну лінію. Ось відповідний код:

    if (codec != null) {
       in = new LineReader(codec.createInputStream(fileIn), job);
       end = Long.MAX_VALUE;
    } else {
       if (start != 0) {
         skipFirstLine = true;
         --start;
         fileIn.seek(start);
       }
       in = new LineReader(fileIn, job);
    }
    if (skipFirstLine) {  // skip first line and re-establish "start".
      start += in.readLine(new Text(), 0,
                        (int)Math.min((long)Integer.MAX_VALUE, end - start));
    }
    this.pos = start;
    

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

Отже, якщо у вас є 2 рядки кожного 100Mb в одному файлі, і для спрощення, скажімо, розмір розділеного шрифту - 64Mb. Тоді, коли вхідні розбиття будуть обчислені, у нас буде такий сценарій:

  • Розділити 1, що містить шлях і хостів до цього блоку. Ініціалізований на старті 200-200 = 0 Мб, довжина 64 Мб.
  • Розділити 2 ініціалізовано на старті 200-200 + 64 = 64Mb, довжина 64Mb.
  • Розділити 3 ініціалізовано на старті 200-200 + 128 = 128Mb, довжина 64Mb.
  • Розділити 4 ініціалізовано на старті 200-200 + 192 = 192Mb, довжина 8Mb.
  • Mapper A обробить розділення 1, початок - 0, тому не пропускайте перший рядок, і читайте повний рядок, який виходить за межі 64Mb, тому потрібне віддалене читання.
  • Mapper B обробить розділення 2, start is! = 0, тому пропустіть перший рядок після 64Mb-1byte, що відповідає кінці рядка 1 на 100Mb, який все ще знаходиться в split 2, у нас є 28Mb рядка в split 2, так дистанційне зчитування решти 72 Мбіт.
  • Mapper C обробить розділення 3, start is! = 0, тому пропустіть перший рядок після 128Mb-1byte, що відповідає кінці рядка 2 на 200Mb, що є кінцем файлу, тому нічого не робіть.
  • Mapper D такий же, як і Map C, за винятком того, що він шукає новий рядок після 192Mb-1byte.

Також @PraveenSripati варто зазначити, що крайові випадки, коли межа буде при \ r у \ r \ n поверненні, обробляються у LineReader.readLineфункції, я не думаю, що це стосується вашого питання, але за потреби можна додати більше деталей.
Чарльз Менгуй

Припустимо, що на вході є два рядки з точними 64 Мб, тому InputSplits відбувається саме на межі лінії. Отож, чи буде завжди ігнорувати картограф рядок у другому блоці, оскільки початок! = 0.
Правен Шріпаті

6
@PraveenSripati У такому випадку другий відображувач буде бачити start! = 0, тому поверніть 1 символ, який поверне вас безпосередньо до \ n першого рядка, а потім пропустіть до наступного \ n. Таким чином, він буде пропускати перший рядок, але обробляти другий рядок, як очікувалося.
Чарльз Менгуй

@CharlesMenguy Чи можливо, що перший рядок файлу якось пропускається? Конкретно, у мене перший рядок з ключем = 1 і значення a, тоді є ще два рядки з тим самим ключем десь у файлі, key = 1, val = b і key = 1, val = c. Річ у тому, що мій редуктор отримує {1, [b, c]} і {1, [a]} замість {1, [a, b, c]}. Це не відбудеться, якщо я додам новий рядок до початку свого файлу. Що може бути причиною, сер?
Кобе-Ван Кенобі

@CharlesMenguy Що робити, якщо файл на HDFS є двійковим файлом (на відміну від текстового файлу, у якому \r\n, \nпредставлено усічення записів)?
CᴴᴀZ

17

Алгоритм зменшення карт не працює на фізичних блоках файлу. Він працює на логічних розбиттях вводу. Розбіжність введення залежить від місця запису. Запис може охоплювати два Mappers.

Спосіб налаштування HDFS розбиває дуже великі файли на великі блоки (наприклад, розміром 128 Мб) і зберігає три копії цих блоків на різних вузлах кластера.

HDFS не обізнаний про вміст цих файлів. Запис, можливо, був запущений у Block-a, але кінець цього запису може бути присутнім у Block-b .

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

Ключовий момент:

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

Подивіться нижче на схему.

введіть тут опис зображення

Перегляньте цю статтю та пов’язане з цим питання SE: Про розбиття файлів Hadoop / HDFS

Детальніше можна прочитати з документації

Рамка Map-Reduce покладається на InputFormat завдання для:

  1. Перевірте вхідну специфікацію завдання.
  2. Розбиття вхідних файлів на логічні InputSplits, кожен з яких призначається окремому Mapper.
  3. Кожен InputSplit потім призначається окремому Mapper для обробки. Спліт може бути кортежем . InputSplit[] getSplits(JobConf job,int numSplits) - API, який повинен опікуватися цими речами.

FileInputFormat , який розширює InputFormatреалізований getSplits() метод. Погляньте на внутрішні відомості цього методу за допомогою grepcode


7

Я вважаю це таким чином: InputFormat несе відповідальність за розподіл даних на логічні розбиття з урахуванням характеру даних.
Ніщо не заважає йому це зробити, хоча це може додати значну затримку в роботі - вся логіка і читання навколо потрібних меж розміру розміру відбуватимуться в трекері пошуку.
Найпростішим форматом введення даних є TextInputFormat. Він працює наступним чином (наскільки я зрозумів з коду) - формат вводу створює розбиття за розміром, незалежно від рядків, але LineRecordReader завжди:
а) Пропустіть перший рядок у розділі (або його частині), якщо він не є перший розкол
b) Прочитайте один рядок після межі розбиття в кінці (якщо дані є, значить, це не останній розкол).


Skip first line in the split (or part of it), if it is not the first split- якщо перший запис у неперше блоці завершений, то не впевнений, як буде працювати ця логіка.
Praveen Sripati

Наскільки я бачу код - кожен розділ читає, що він має + наступний рядок. Тож якщо розрив рядка не знаходиться на межі блоку - це нормально. Як саме вирішується випадок, коли розрив рядка знаходиться точно на межі блоку - треба зрозуміти - я прочитаю код трохи більше
Девід Грузман

3

З того, що я зрозумів, коли FileSplitініціалізується перший блок, викликається конструктор за замовчуванням. Тому значення для початку та довжини спочатку дорівнюють нулю. До кінця обробки блоку кулака, якщо останній рядок буде неповним, то значення довжини буде більше, ніж довжина розщеплення, і він також прочитає перший рядок наступного блоку. У зв'язку з цим значення початку для першого блоку буде більше нуля, і за цієї умови, LineRecordReaderлінія кулака другого блоку буде пропускати. (Див. Джерело )

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

Має сенс?


2
У цьому сценарії картографи повинні спілкуватися один з одним і обробляти блоки послідовно, коли останній рядок у певному блоці не завершений. Не впевнений, чи так це працює.
Praveen Sripati

1

З вихідного коду hadoop конструктора LineRecordReader.java: я знайшов деякі коментарі:

// If this is not the first split, we always throw away first record
// because we always (except the last split) read one extra line in
// next() method.
if (start != 0) {
  start += in.readLine(new Text(), 0, maxBytesToConsume(start));
}
this.pos = start;

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


0

Картографам не потрібно спілкуватися. Файлові блоки знаходяться у форматі HDFS і може поточний картограф (RecordReader) може прочитати блок, що містить частину рядка. Це відбувається за лаштунками.

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