В этом посте мы расскажем о том, как передать параметры конфигурации в 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-7ae84b687f9cSusie's Casuals|81a43486-07e1-4b92-b92b-03d0caa87b5fSuper Saver Foods|aef52cf1-f565-4124-bf18-47acdac47a0e..... |
oneToManyVehicles2.txt:
|
1
2
3
4
|
2003 Holden Cruze#cdd8dde3-0349-4f0d-b97a-7ae84b687f9c2012 Volkswagen T5#81a43486-07e1-4b92-b92b-03d0caa87b5f2009 Renault Trafic#aef52cf1-f565-4124-bf18-47acdac47a0e..... |
singlePersonRecords.txt:
|
1
2
3
4
|
cdd8dde3-0349-4f0d-b97a-7ae84b687f9c,Esther,Garner,4071 Haven Lane,Okemos,MI81a43486-07e1-4b92-b92b-03d0caa87b5f,Timothy,Duncan,753 Stadium Drive,Taunton,MAaef52cf1-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 Showrooms0c521380-f868-438c-9916-4ab4ea76d316,Robert,Eversole,518 Stratford Court,Fayetteville,NC,2002 Toyota Highlander,Specialty Restaurant Group1303e8a6-0085-45b1-8ea5-26c809635da1,Joe,Nagy,3438 Woodstock Drive,El Monte,CA,2011 Hyundai ix35,Eagle Food Centers15360125-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 уменьшить количество рабочих мест