Как записи процесса Hadoop разбиваются по границам блоков?

В соответствии с Hadoop - The Definitive Guide

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

Предположим, что линия записи разбита на два блока (b1 и b2). Обработчик, обрабатывающий первый блок (b1), заметит, что последняя строка не имеет разделителя EOL и выбирает оставшуюся строку из следующего блока данных (b2).

Как обработчик, обрабатывающий второй блок (b2), определяет, что первая запись неполна и должна обрабатываться, начиная со второй записи в блоке (b2)?

Ответ 1

Интересный вопрос, я потратил некоторое время на просмотр кода для деталей, и вот мои мысли. Разделения обрабатываются клиентом с помощью InputFormat.getSplits, поэтому просмотр FileInputFormat дает следующую информацию:

  • Для каждого входного файла получите длину файла, размер блока и вычислите размер разделения как max(minSize, min(maxSize, blockSize)), где maxSize соответствует mapred.max.split.size и minSize is mapred.min.split.size.
  • Разделите файл на другой FileSplit на основе раздельного размера, рассчитанного выше. Важно то, что каждый 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, он пытается создать экземпляр 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;
    

Итак, так как расщепления вычисляются на клиенте, mappers не нужно запускать последовательно, каждый разработчик уже знает, нужно ли отбрасывать первую строку или нет.

Итак, в основном, если у вас есть 2 строки каждого 100 Мб в одном файле, и для упрощения пусть говорят, что размер разделения равен 64 МБ. Затем, когда вычисляются входные расщепления, мы будем иметь следующий сценарий:

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

Ответ 2

Алгоритм Reduece Map не работает на физических блоках файла. Он работает с логическими входными расщеплениями. Разделение входа зависит от того, где была записана запись. Запись может охватывать два Mappers.

Способ HDFS был настроен, он разбивает очень большие файлы на большие блоки (например, размером 128 МБ) и сохраняет три копии этих блоков на разных узлах кластера.

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

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

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

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

Посмотрите ниже диаграмму.

enter image description here

Взгляните на эту статью и связанный с ней вопрос SE: о разделении файлов Hadoop/HDFS

Более подробную информацию можно получить из документации

Структура Map-Reduce основывается на InputFormat задания:

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

FileInputFormat, который расширяет InputFormat реализует getSplits(). Взгляните на внутренности этого метода на grepcode

Ответ 3

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

б) Прочитайте одну строку после границы разделения в конце (если данные доступны, поэтому это не последний раскол).

Ответ 4

Из того, что я понял, когда инициализируется FileSplit для первого блока, вызывается конструктор по умолчанию. Поэтому значения для начала и длины изначально равны нулю. По завершении обработки блока кулачка, если последняя строка неполна, тогда значение длины будет больше, чем длина раскола, и он также прочитает первую строку следующего блока. В связи с этим значение начала для первого блока будет больше нуля, и при этом условии LineRecordReader пропустит первую часть второго блока. (См. source)

В случае завершения последней строки первого блока значение длины будет равно длине первого блока, а значение начала для второго блока будет равно нулю. В этом случае LineRecordReader не пропустит первую строку и прочитает второй блок с начала.

Имеет смысл?

Ответ 5

Мапперам не нужно общаться. Файловые блоки находятся в HDFS, и текущий обработчик (RecordReader) может считывать блок с оставшейся частью строки. Это происходит за кулисами.

Ответ 6

Из исходного кода из базы данных 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 будет читать одну дополнительную строку для каждого раскола (в конце текущего раскола, читать следующую строку в следующем расколе), и если не первый раскол, первая строка будет выброшена. так что никакая запись линии не будет потеряна и неполна