В первом посте этой серии мы рассмотрели, как 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 .