Статьи

Группирование, мультиплексирование и объединение в Hadoop — часть 2

В  первом посте этой серии мы рассмотрели, как  MultipleOutputFormat класс можно использовать в задаче для записи в несколько выходных файлов. У этого подхода было несколько недостатков, которые заключались в том, что его нельзя было использовать на стороне задания, в котором использовались редукторы, и он работал только со старым  mapred API.

В этом посте мы рассмотрим  MultipleOutputs класс, который предлагает альтернативу,  MultipleOutputFormat а также устраняет ее недостатки.

MultipleOutputs

Использование  MultipleOutputs класса — это более современный способ записи Hadoop на несколько выходов. Он имеет  mapredи  mapreduce реализацию API, и позволяет работать с несколькими классами OutputFormat в вашей работе. Его подход отличается от  MultipleOutputFormat — вместо того, чтобы определять свой собственный,  OutputFormat он просто предоставляет некоторые вспомогательные методы, которые должны вызываться в вашем коде драйвера, а также в вашем преобразователе / ​​преобразователе.

Два  MultipleOutputs класса  mapred и  mapreduce по функциональности близки, главное отличие заключается в поддержке выходов с несколькими именами, которые мы рассмотрим позже в этом посте.

Давайте посмотрим, как мы могли бы достичь того же результата, что и мы  MultipleOutputFormat. Если вы помните из предыдущего поста в этой серии, мы работали с некоторыми образцами данных с фруктового рынка, где точками данных были местоположение каждого рынка и проданный фрукт:

cupertino   apple
sunnyvale   banana
cupertino   pear

Наша цель состоит в том, чтобы разбить выходные данные по городам, чтобы были файлы, специфичные для города. Прежде всего, это наш код драйвера, где нам нужно сообщить  MultipleOutputs именованные выходные данные и связанные с ними  OutputFormat классы. Для простоты мы выбрали  TextOutputFormat оба варианта, но вы можете использовать разные  OutputFormats для каждого именованного вывода.

MultipleOutputs.addNamedOutput(jobConf, "cupertino", TextOutputFormat.class, Text.class, Text.class);
MultipleOutputs.addNamedOutput(jobConf, "sunnyvale", TextOutputFormat.class, Text.class, Text.class);

Именованные выходы «cupertino» и «sunnyvale» используются для двух целей: во-  MultipleOutputs первых, в качестве логических ключей, которые вы используете в своем преобразователе и редукторе для поиска связанных с ними  OutputCollector классов. И, во-вторых, они используются в качестве выходных файлов в HDFS.

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

class Reduce extends MapReduceBase
        implements Reducer<Text, Text, Text, Text> {

    private MultipleOutputs output;

    @Override
    public void configure(final JobConf job) {
        super.configure(job);
        output = new MultipleOutputs(job);
    }

    @Override
    public void reduce(final Text key, final Iterator<Text> values,
                       final OutputCollector<Text, Text> collector, final Reporter reporter)
            throws IOException {
        while (values.hasNext()) {
            output.getCollector(key.toString(), reporter).collect(key, values.next());
        }
    }
}

Как вы можете, вы не используете  OutputCollector предоставленный нам в  reduce методе. Вместо этого вы создаете MultipleOutputs экземпляр в  configure методе, который используется в методе Reduce. Для каждой входной записи редуктора мы используем ключ для поиска  OutputCollector и затем посылаем каждую пару ключ / значение в этот коллектор. Помните, что при вызове  getCollector вы должны использовать один из именованных выходов, которые вы определили в драйвере задания. В нашем случае нашими клавишами ввода являются «cupertino» или «sunnyvale», и они отображаются непосредственно на именованные выходы, которые мы определили в нашем драйвере, поэтому мы в хорошей форме.

Давайте проверим содержимое каталога вывода задания после запуска задания.

$ hadooop -lsr /output
/output/cupertino-r-00000
/output/sunnyvale-r-00000
/output/part-00000
/output/part-00001

Этот вывод подчеркивает одно из ключевых различий между  MultipleOutputs и  MultipleOutputFormat. При использовании  MultipleOutputs вы можете выводить на обычный редуктор  OutputCollector, или  OutputCollector на именованный выход, или на оба, поэтому вы видите  part-nnnnn файлы.

Но ждать! Одна из проблем  MultipleOutputs заключается в том, что вам необходимо заранее определить разделы «купертино» и «солнечный сезон» в нашем драйвере. Что если мы не знали разделов раньше времени?

Динамические файлы с классом MultipleOutputs

До сих  MultipleOutputs пор к нам относились хорошо — он поддерживал как старый, так и новый API MapReduce, а также мог поддерживать несколько классов OutputFormat в одном и том же редукторе. Но, как мы видели, нам, по сути, пришлось предварительно определить выходные файлы в нашем коде драйвера. Итак, как нам обращаться со случаями, когда мы хотим, чтобы это динамически выполнялось в редукторе?

К счастью, у них  MultipleOutputs есть понятие «многоименованный» вывод. В методе драйвера вместо перечисления всех выходных файлов, которые мы хотим, мы просто добавим одно логическое имя с именем «fruit», используя  addMultiNamedOutput вместо  addNamedOutput:

MultipleOutputs.addMultiNamedOutput(jobConf, "fruit", TextOutputFormat.class, Text.class, Text.class);

В нашем редукторе мы всегда указываем «фрукты» в качестве имени, но мы используем другой  getCollector метод, который принимает дополнительное поле, которое используется для определения имени файла, которое используется для вывода:

output.getCollector("fruit", key.toString(), reporter).collect(key, values.next());

Давайте сделаем еще один листинг HDFS:

$ hadooop -lsr /output
/output/fruit_cupertino-r-00000
/output/fruit_sunnyvale-r-00000
/output/part-00000
/output/part-00001

Ура! Теперь у нас есть несколько выходных файлов, которые динамически создаются на основе выходного ключа редуктора, как мы это делали с  MultipleOutputFormat.

К сожалению, вывод с несколькими именами поддерживается только старым  mapred API, тогда как с новым mapreduce API вы вынуждены определять свои разделы в драйвере задания.

Вывод

Есть много вещей, которые могут понравиться  MultipleOutputs, а именно: поддержка «старых» и «новых» API MapReduce, а также поддержка нескольких  OutputFormat классов. Единственный его недостаток в том, что многоименованные выходные данные поддерживаются только в старом  mapred API, поэтому те, кто ищет динамические разделы в новом  mapreduce API, не поддерживаются ни одним из них  MultipleOutputs или не  MultipleOutputFormat описаны в  части 1 .