В этом посте мы расскажем о том, как передать параметры конфигурации в Hadoop Mapper через объект Context. Как правило, мы устанавливаем параметры конфигурации как пары ключ / значение в объекте Context при запуске задания сокращения карты. Затем в Mapper мы используем ключ (и) для получения значений, которые необходимо использовать для наших потребностей в конфигурации. Суть в том, что мы установим на Context
объекте специально отформатированную строку , а при извлечении значения в Mapper используйте Guava MapSplitter
для преобразования отформатированной строки вHashMap
это будет использоваться для получения параметров конфигурации. Мы можем спросить себя: «Зачем идти на эту неприятность?» Выполнив настройку таким образом, мы можем передать несколько параметров в Mapper с одной парой ключ-значение, установленной в объекте Context. Чтобы проиллюстрировать одно возможное использование, мы вернемся к последнему сообщению, где мы рассмотрели, как выполнить соединения на стороне уменьшения. В этом посте есть две проблемы с предлагаемым решением. Во-первых, мы предполагаем, что ключ для присоединения всегда является первым значением в строке с разделителями из файла. Во-вторых, мы предполагаем, что для каждого файла используется один и тот же разделитель. Что если мы хотим объединить данные из файлов, где ключ находится в разных местах на файл, а некоторые файлы используют разные разделители? Кроме того, мы хотим использовать один и тот же разделитель (если есть) для всех данных, которые мы выводим, независимо от разделителя, используемого в любом из входных файлов. Хотя это, по общему признанию, надуманная ситуация, она хорошо послужит для демонстрационных целей. Сначала давайте рассмотрим, что такое MapSplitter
класс и как мы можем его использовать.
MapSplitter
Это MapSplitter
вложенный класс в Splitter
классе. Он Spitter
берет строку и разбивает ее на части с заданным разделителем. MapSplitter идет дальше, создав Map <String, String> из строки, в которой пары ключ-значение разделены одним разделителем, а сами пары разделены другим разделителем. Давайте посмотрим на пример:
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
. Наш файл свойств будет выглядеть так:
oneToManyEmployer2.txt=keyIndex=1&separator=| oneToManyVehicles2.txt=keyIndex=1&separator=#
Здесь мы указываем, что файл oneToManyEmployer2.txt имеет наш ключ соединения в позиции индекса 1, а разделитель — «|» символ канала и файл oneToManyVehicles2.txt имеет ключ соединения в позиции индекса 1 и использует запятую «,» в качестве разделителя. Мы собираемся внести несколько изменений в наш класс водителей. Сначала мы собираемся загрузить файл свойств (при условии, что мы поместили файл в каталог, расположенный относительно того места, где мы вызываем Hadoop).
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
переменную, которая предоставляет значения по умолчанию, если свойство не найдено для файла. Мы также добавили некоторые дополнительные конфигурационные ключи и значения; разделитель, используемый при объединении значений, и порядок соединения для файла, который определяется его положением в аргументах, предоставляемых программе. Затем мы просто помещаем отформатированную строку в Context
объект, используя имя файла в качестве ключа.
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
сохраненную строку, содержащую наши параметры конфигурации.
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
переменную экземпляра для создания нашего HashMap
, извлекая отформатированную строку, хранящуюся в Context
имени файла, используемого этим Mapper. Теперь мы можем просто извлечь необходимые параметры конфигурации из карты, как показано здесь в setup
методе:
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:
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:
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:
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
......
После выполнения нашей работы по уменьшению карты наши результаты выглядят именно так, как мы хотим:
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
......
Resources
Resources
- Data-Intensive Processing with MapReduce by Jimmy Lin and Chris Dyer
- Hadoop: The Definitive Guide by Tom White
- Source Code and Tests from blog
- Programming Hive by Edward Capriolo, Dean Wampler and Jason Rutherglen
- Programming Pig by Alan Gates
- Hadoop API
- MRUnit for unit testing Apache Hadoop map reduce jobs