Статьи

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

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

Ресурсы

Ссылка: Настройка Hadoop с помощью Guava MapSplitters от нашего партнера по JCG Билла Бекака в блоге Randomечет о кодировании .