В этом посте мы расскажем о том, как передать параметры конфигурации в Hadoop Mapper через объект Context. Как правило, мы устанавливаем параметры конфигурации как пары ключ / значение в объекте Context при запуске задания сокращения карты. Затем в Mapper мы используем ключ (и), чтобы получить значение (я) для использования в наших конфигурационных потребностях. Суть в том, что мы установим специально отформатированную строку в объекте Context
и при получении значения в Mapper используйте Guava MapSplitter
чтобы преобразовать отформатированную строку в HashMap
который будет использоваться для получения параметров конфигурации. Мы можем спросить себя, зачем идти на эту неприятность? Выполнив конфигурацию таким образом, мы можем передать несколько параметров в Mapper с одной парой ключ-значение, установленной в объекте Context. Чтобы проиллюстрировать одно возможное использование, мы вернемся к последнему посту , где мы рассмотрели, как выполнять соединения на стороне уменьшения. В этом посте есть две проблемы с предлагаемым решением. Во-первых, мы предполагаем, что ключ для присоединения всегда является первым значением в строке с разделителями из файла. Во-вторых, мы предполагаем, что для каждого файла используется один и тот же разделитель. Что если мы хотим объединить данные из файлов, где ключ находится в разных местах на файл, а некоторые файлы используют разные разделители? Кроме того, мы хотим использовать один и тот же разделитель (если есть) для всех данных, которые мы выводим, независимо от разделителя, используемого в любом из входных файлов. Хотя это, по общему признанию, надуманная ситуация, она хорошо послужит для демонстрационных целей. Сначала давайте рассмотрим, что MapSplitter
класс MapSplitter
и как мы можем его использовать.
MapSplitter
MapSplitter
— это вложенный класс в классе Splitter
. Spitter
берет строку и разбивает ее на части с заданным разделителем. MapSplitter идет дальше, создав Map <String, String> из строки, где пары ключ-значение разделены одним разделителем, а сами пары разделены другим разделителем. Давайте посмотрим на пример:
1
2
3
|
Map<String,String> configParams = Splitter.splitOn( "#" ) .withKeyValueSeparator( "=" ) .split( "6=June#7=July#8=August" ); |
В приведенном выше примере строка "6=June#7=July#8=August"
будет преобразована в карту с ключами 6,7 и 8, сопоставленными соответственно с июнем, июлем и августом. MapSplitter
— очень простой, но мощный класс. Теперь мы знаем, как работает MapSplitter
, давайте посмотрим, как мы можем использовать его, чтобы помочь нам установить параметры конфигурации для наших заданий по уменьшению карты.
Конфигурация с использованием MapSplitter
Ранее мы устанавливали индексную позицию нашего ключа соединения и разделителя для разделения на одинаковые для всех файлов, устанавливая значения в объекте Context
для задания карты-сокращения. Теперь мы хотим иметь возможность устанавливать их для каждого входного файла по мере необходимости. Мы по-прежнему будем иметь значения по умолчанию при необходимости. Чтобы выполнить это изменение, мы создадим файл свойств, в котором в качестве ключа будет указано имя файла, а значением будет строка, отформатированная для использования MapSplitter
. Наш файл свойств будет выглядеть так:
1
2
|
oneToManyEmployer2.txt=keyIndex= 1 &separator=| oneToManyVehicles2.txt=keyIndex= 1 &separator=# |
Здесь мы указываем, что файл oneToManyEmployer2.txt имеет наш ключ соединения в позиции индекса 1, а разделитель — «|» символ pipe и файл oneToManyVehicles2.txt имеют ключ соединения в позиции индекса 1 и используют запятую «,» в качестве разделителя. Мы собираемся внести несколько изменений в наш класс водителей. Сначала мы собираемся загрузить файл свойств (при условии, что мы поместили файл в каталог, расположенный относительно того места, где мы вызываем hadoop).
1
2
3
|
InputStream inputStream = new FileInputStream( new File( "./jobs/join-config.properties" )); Properties properties = new Properties(); properties.load(inputStream); |
Сначала мы определяем обычный объект Splitter
, который разделит имена файлов на косую черту ‘/’. Затем, перебирая имена файлов, мы получаем базовое имя файла, вызывая Iterables.getLast
для объекта Iterable
возвращаемого из вызова метода Splitter.split
. Затем мы пытаемся получить настроенную строку свойств для каждого файла в методе Properties.getProperty
. Обратите внимание, что мы также defaultMapConfig
переменную defaultMapConfig
которая предоставляет значения по умолчанию, если свойство не найдено для файла. Мы также добавили некоторые дополнительные конфигурационные ключи и значения; разделитель, используемый при объединении значений, и порядок соединения для файла, который определяется его положением в аргументах, предоставляемых программе. Затем мы просто помещаем отформатированную строку в объект Context
используя имя файла в качестве ключа.
01
02
03
04
05
06
07
08
09
10
|
String defaultMapConfig = "keyIndex=0&separator=," ; Splitter splitter = Splitter.on( '/' ); for ( int i = 0 ; i < args.length - 1 ; i++) { String fileName = Iterables.getLast(splitter.split(args[i])); String mapConfig = properties.getProperty(fileName, defaultMapConfig); builder.append(mapConfig).append( "&joinDelimiter=,&joinOrder=" ).append(i + 1 ); config.set(fileName, builder.toString()); builder.setLength( 0 ); filePaths.append(args[i]).append( "," ); } |
Использование значений конфигурации
Чтобы использовать наши значения конфигурации, нам сначала нужно получить HashMap
хранящийся в виде строки, содержащей наши параметры конфигурации.
1
2
3
4
5
6
7
|
private Splitter.MapSplitter mapSplitter = Splitter.on( "&" ).withKeyValueSeparator( "=" ); ....... private Map<String,String> getConfigurationMap(Context context){ FileSplit fileSplit = (FileSplit)context.getInputSplit(); String configString = context.getConfiguration().get(fileSplit.getPath().getName()); return mapSplitter.split(configString); } |
Здесь мы используем MapSplitter
экземпляра MapSplitter
и создаем наш HashMap
, извлекая отформатированную строку, хранящуюся в Context
с именем файла, используемого этим Mapper. Теперь мы можем просто извлечь необходимые параметры конфигурации из карты, как показано здесь в методе setup
:
1
2
3
4
5
6
7
8
9
|
protected void setup(Context context) throws IOException, InterruptedException { Map<String,String> configMap = getConfigurationMap(context); keyIndex = Integer.parseInt(configMap.get( "keyIndex" )); String separator = configMap.get( "separator" ); splitter = Splitter.on(separator).trimResults(); String joinDelimiter = configMap.get( "joinDelimiter" ); joiner = Joiner.on(joinDelimiter); joinOrder = Integer.parseInt(configMap.get( "joinOrder" )); } |
Код в методе map
остается таким же, как и в предыдущем посте. Теперь у нас есть полностью настраиваемые параметры для файла, и мы не ограничены наличием ключа соединения в одной позиции или использованием одного и того же разделителя для файла. Конечно, это только один пример, но описанный здесь подход можно использовать для настройки многих других параметров, и для него требуется только один ключ в объекте Context
.
Полученные результаты
Изначально наши данные выглядели так:
oneToManyEmployer2.txt:
1
2
3
4
|
Creative Wealth|cdd8dde3-0349-4f0d-b97a-7ae84b687f9c Susie's Casuals|81a43486-07e1-4b92-b92b-03d0caa87b5f Super Saver Foods|aef52cf1-f565-4124-bf18-47acdac47a0e ..... |
oneToManyVehicles2.txt:
1
2
3
4
|
2003 Holden Cruze #cdd8dde3-0349-4f0d-b97a-7ae84b687f9c 2012 Volkswagen T5 #81a43486-07e1-4b92-b92b-03d0caa87b5f 2009 Renault Trafic #aef52cf1-f565-4124-bf18-47acdac47a0e ..... |
singlePersonRecords.txt:
1
2
3
4
|
cdd8dde3-0349-4f0d-b97a-7ae84b687f9c,Esther,Garner,4071 Haven Lane,Okemos,MI 81a43486-07e1-4b92-b92b-03d0caa87b5f,Timothy,Duncan,753 Stadium Drive,Taunton,MA aef52cf1-f565-4124-bf18-47acdac47a0e,Brett,Ramsey,4985 Shinn Street,New York,NY ...... |
После выполнения нашей работы по уменьшению карты наши результаты выглядят именно так, как мы хотим:
1
2
3
4
5
|
08db7c55-22ae-4199-8826-c67a5689f838,John,Gregory,258 Khale Street,Florence,SC,2010 Nissan Titan,Ellman's Catalog Showrooms 0c521380-f868-438c-9916-4ab4ea76d316,Robert,Eversole,518 Stratford Court,Fayetteville,NC,2002 Toyota Highlander,Specialty Restaurant Group 1303e8a6-0085-45b1-8ea5-26c809635da1,Joe,Nagy,3438 Woodstock Drive,El Monte,CA,2011 Hyundai ix35,Eagle Food Centers 15360125-38d6-4f1e-a584-6ab9d1985ab8,Sherri,Hanks,4082 Old House Drive,Alexandria,OH,2003 Toyota Solara,Odyssey Records & Tapes ...... |
Ресурсы
- Интенсивная обработка данных с MapReduce Джимми Лином и Крисом Дайером
- Hadoop: полное руководство Тома Уайта
- Исходный код и тесты из блога
- Программирующий улей Эдварда Каприоло, Дина Уэмплера и Джейсона Рутерглена
- Свинья Программирования Аланом Гейтсом
- Hadoop API
- MRUnit для модульного тестирования Apache Hadoop map уменьшить количество рабочих мест