Статьи

Настройка Hadoop с помощью Guava MapSplitters

Hadoop-логотипВ этом посте мы расскажем о том, как передать параметры конфигурации в 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