- PVSM.RU - https://www.pvsm.ru -
Доброго времени суток, дорогое читатели. Не так давно я начал изучать работу с большими данными (Map/Reduce, NoSQL...) и очень быстро узнал о фреймворке с открытым исходным кодом Apache Hadoop, за изучение которого сразу и принялся.
Данный пост рассчитан на новичков, которые тоже не так давно начали изучать Hadoop. В посте будет разобрано небольшое приложение построенное на этом фреймворке(Этакий Hello World!). Кому интересно, добро пожаловать под кат.
Данный топик не рассматривает процесс установки, настройки и проблем с запуском, однако ресурсы для изучения вы можете посмотреть внизу. Мною в работе были использованы следующие технологии:
По скольку счетчик слов (он же Word Count) продемонстрирован в подавляющем большинстве туториалов, я решил разнообразить эту тему и в качестве примера разобрал grep [1].
Наша реализация будет получать на вход:
На выходе мы получим файл(ы) которые содержат полные пути к файлам(ключи) в которых нашлись совпадения и строки(значения) с этими совпадениями в файле.
Весь процесс обработки данных построен на парадигме MapReduce [3]. Суть ее в том, что мы разделяем всю работу на два этапа: map и reduce.
Итак, преступим.
На этом шаге мы в качестве аргумента получаем ключ и значение. Далее эти данные проходят обработку подавая на выход список ключей и список значений.
Наша реализация map функции:
/*
* Маппер.
* Пример построен с использованием нового API org.apache.hadoop.mapreduce.*
*/
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* LongWritable - Тип входного ключа(Номер строки).
* Text - Тип входного значения (Строка под номером ключа).
* Text - Тип выходного ключа(Полный путь к файлу).
* Text - Тип выходного значения(Строка из файла с совпадением).
*/
public class RegexMapper extends Mapper<LongWritable, Text, Text, Text>{
private Pattern pattern;
private Text keyOut; //В качестве ключа на выход будет взят полный путь к файлу.
/*
* Метод setup() вызывается перед вызовом метода map()(который переопределен ниже).
* Используется для отделения подготовительных действий от самой map() функции.
*/
@Override
public void setup(Context context) throws IOException{
/*
* Берем сохраненный аргумент(регулярное выражение),
* который был сохранен в Driver-классе(будет описан далее).
*/
pattern = Pattern.compile(context.getConfiguration().get("regex"));
//Получаем полный путь входной строки файла (valueIn).
Path filePath = ((FileSplit) context.getInputSplit()).getPath();
keyOut = new Text(filePath.toString());
}
/*
* Сам map() метод. Создаем матчер и ищем в строке совпадения. В случае нахожде-
* ния записываем полный путь файла в качестве ключа(keyOut - получен в setup()
* методе) и строку из этого файла в качестве значения(valueIn - получена
* как аргумент метода).
*/
@Override
public void map(LongWritable key, Text valueIn, Context context)
throws IOException, InterruptedException {
Matcher matcher = pattern.matcher(valueIn.toString());
//По скольку входным значением является только одна строка файла, то нам доста-
//точно найти хотя бы одно совпадение, что бы строка подходила условиям поиска.
if (matcher.find())
context.write(keyOut, valueIn); //Запись пары ключ значение
}
}
Вот и все. Переходим к reduce.
На этапе reduce мы получаем в качестве аргумента один ключ и все соответствующие ему значения полученные на выходе map метода(ов) для их последующей обработки. В нашем случае мы получили путь к файлу, где найден текст соответствующий заданному шаблону(ключ) и набор строк, где были найдены совпадения(список значений).
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/*
*Все ключи и значения типа Text
*/
public class RegexReducer extends Reducer<Text, Text, Text, Text> {
/*
* В методе мы форматируем полученные данные для записи в файл.
*/
@Override
public void reduce(Text keyIn, Iterable<Text> valuesIn, Context context)
throws IOException, InterruptedException {
//Для конкатенации строк воспользуемся StringBuilder.
StringBuilder valueOut = new StringBuilder();
for(Text value: valuesIn)
valueOut.append("n" + value.toString());
valueOut.append("n");
context.write(keyIn, new Text(valueOut.toString()));
}
}
С map и reduce разобрались. Осталось это все упаковать в класс-драйвер и запустить.
В классе-драйвере происходит настройка задачи(установка маппера и редусера, типа входных и выходных данных и т. д.).
В общем вот:
import com.petrez.mappers.RegexMapper;
import com.petrez.reducers.RegexReducer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.ToolRunner;
import java.io.IOException;
public class Grep {
public static void main(String[] args)
throws IOException, ClassNotFoundException, InterruptedException {
if(args.length != 3) {
System.out.println("Usage: <inDir> <outDir> <regex>");
ToolRunner.printGenericCommandUsage(System.out);
System.exit(-1);
}
Configuration config = new Configuration();
/*
* Сохраняем регулярное выражение для map() метода с ключом regex.
*/
config.set("regex", args[2]);
Job job = new Job(config, "grep");
/*
* Я запускаю программу из jar-файла, поэтому указание главного класса
* приложения необходимо.
*/
job.setJarByClass(Grep.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
/*
* Вот. TextInputFormat разбивает входные файлы на строки и подает их
* в качестве аргумента map функциям. В качестве
* разделителя используется символ возврата каретки.
*/
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.setMapperClass(RegexMapper.class);
job.setReducerClass(RegexReducer.class);
job.waitForCompletion(true);
}
}
В силу того, что реализация рассчитана для начинающих, она упрощена в ущерб эффективности, а именно создание на каждую строку по отдельному мапперу. Данный вариант очень упрощает реализацию маппера и редусера, но сильно нагружает память. Прошу учесть.
По скольку я все упаковал в исполняемый jar-файл, то запустить нашу программку можно так:
<путь к hadoop>/bin/hadoop jar /home/hduser/HadoopGrep.jar <путь с файлами для анализа> <путь для сохранения результатов> <регулярное выражение>
Путем для сохранения результатов должна быть несуществующая директория. Если Вы настроили Hadoop в pseudo-distributed mode, то данные сохраняются в файловой системе HDFS и вам их оттуда еще нужно будет вытащить.
Материалы для изучения:
Всем спасибо.
Автор: methode
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/40736
Ссылки в тексте:
[1] grep: https://ru.wikipedia.org/wiki/Grep
[2] Регулярное выражение: http://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F
[3] MapReduce: http://ru.wikipedia.org/wiki/MapReduce
[4] Хороший туториал по установке Hadoop: http://www.michael-noll.com/tutorials/running-hadoop-on-ubuntu-linux-single-node-cluster/
[5] Очень рекомендую статьи на Yahoo: http://developer.yahoo.com/hadoop/tutorial/
[6] Hadoop Stable Version API: http://hadoop.apache.org/docs/stable/api/
[7] Описание MapReduce парадигмы: http://static.googleusercontent.com/external_content/untrusted_dlcp/research.google.com/ru//archive/mapreduce-osdi04.pdf
[8] Источник: http://habrahabr.ru/post/189798/
Нажмите здесь для печати.