Если вы когда-нибудь задумывались, каково это — укротить дикого зверя по имени Apache Hadoop, создавая собственные плагины, вас ждёт настоящее приключение. Представьте Hadoop как надёжного, но иногда своенравного друга, который способен справиться с огромными объёмами работы, но для этого ему нужны очень конкретные инструкции. Сегодня мы погрузимся в искусство разработки плагинов для Hadoop на Java, и поверьте, это увлекательнее, чем смотреть, как краска сохнет на стойке сервера.

Подготовка: понимание архитектуры плагинов Hadoop

Прежде чем мы начнём орудовать клавиатурами Java как цифровыми самурайскими мечами, давайте разберёмся, с чем имеем дело. Архитектура плагинов Hadoop построена вокруг концепции расширяемости — это как иметь швейцарский армейский нож, который позволяет вам добавлять собственные инструменты.

Прелесть Hadoop заключается в его фреймворке MapReduce, который разбивает сложные задачи обработки данных на управляемые части. Когда вы разрабатываете плагин, вы, по сути, создаёте специализированный инструмент, который органично вписывается в этот распределённый конвейер обработки.

graph TD A[Входные данные] --> B[Ваш собственный плагин] B --> C[Этап карты] C --> D[Перемешивание и сортировка] D --> E[Этап сокращения] E --> F[Результаты вывода] G[Фреймворк Hadoop] --> B G --> C G --> E

Подготовка среды разработки: основа совершенства

Давайте приступим к настройке. Вам понадобится несколько основных инструментов в наборе разработчика, и нет, кофеин в официальном списке не указан (хотя он должен быть).

Основные инструменты и зависимости

Первое, что нужно сделать, — настроить Eclipse IDE для разработки Hadoop. Процесс несложный, но требует внимания к деталям:

  1. Скачать и установить Eclipse IDE.
  2. Переместить папку eclipse в домашний каталог.
  3. Скачать необходимые JAR-файлы:
  • hadoop-core-1.2.1.jar
  • commons-cli-1.2.jar

Конфигурация Maven: супергерой вашей системы сборки

Maven станет вашим лучшим другом в этом путешествии. Это как иметь личного помощника, который никогда не забывает включить нужные зависимости и всегда идеально упаковывает всё.

Создайте файл pom.xml, который будет выглядеть примерно так:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>hadoop-custom-plugin</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <hadoop.version>2.7.1</hadoop.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>${hadoop.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>${hadoop.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.example.CustomHadoopPlugin</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Конфигурация maven-jar-plugin имеет решающее значение, потому что она сообщает Maven, какой класс содержит ваш основной метод — думайте об этом как о координатах GPS для начальной точки вашего приложения.

Создание первого плагина Hadoop: пример инвертированного индекса

Теперь самое интересное — создадим настоящий работающий плагин Hadoop. Мы создадим генератор инвертированного индекса, который похож на создание исчерпывающего оглавления для огромных наборов данных. Это невероятно полезно для поисковых систем и приложений для анализа текста.

Основной класс драйвера

Каждому плагину Hadoop нужен класс драйвера — это дирижёр вашего распределённого оркестра:

package com.example;
import java.io.IOException;
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.output.FileOutputFormat;
public class CustomHadoopPlugin {
    public static void main(String[] args) throws IOException,
                                                  ClassNotFoundException,
                                                  InterruptedException {
        if (args.length != 2) {
            System.err.println("Использование: CustomHadoopPlugin <входной путь> <выходной путь>");
            System.exit(-1);
        }
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "custom inverted index");
        // Установка jar-файла, содержащего драйвер, маппер и редукер
        job.setJarByClass(CustomHadoopPlugin.class);
        // Установка классов маппера и редуктора
        job.setMapperClass(InvertedIndexMapper.class);
        job.setCombinerClass(InvertedIndexReducer.class);
        job.setReducerClass(InvertedIndexReducer.class);
        // Установка типов выходных ключей и значений
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);
        // Установка входных и выходных путей
        FileInputFormat.addInputPath(job, new Path(args));
        FileOutputFormat.setOutputPath(job, new Path(args));
        // Ожидание завершения задания
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

Маппер: где начинается магия

Маппер — это место, где начинается преобразование данных. Это как команда очень сосредоточенных библиотекарей, которые берут на себя раздел книг и составляют подробные каталоги:

package com.example;
import java.io.IOException;
import java.util.StringTokenizer;
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;
public class InvertedIndexMapper extends Mapper<LongWritable, Text, Text, Text> {
    private Text word = new Text();
    private Text documentId = new Text();
    @Override
    protected void map(LongWritable key, Text value, Context context)
                      throws IOException, InterruptedException {
        // Получение имени файла из входного раздела
        String filename = ((FileSplit) context.getInputSplit()).getPath().getName();
        documentId.set(filename);
        // Преобразование строки в нижний регистр и разделение на токены
        String line = value.toString().toLowerCase();
        StringTokenizer tokenizer = new StringTokenizer(line, ".,;!?\\t\\n\\r\\f ()[]{}");
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken().trim();
            // Пропуск пустых токенов и очень коротких слов
            if (token.length() > 2) {
                word.set(token);
                context.write(word, documentId);
            }
        }
    }
}

Обратите внимание, как мы используем StringTokenizer с комплексным набором разделителей. Этот подход гарантирует, что мы учтём все крайние случаи — потому что никому не нравится, когда обработка текста пропускает пунктуацию и создаёт «слова» вроде «hello,world».

Редуктор: объединение всего воедино

Редуктор — ваш эксперт по консолидации данных. Он собирает всю разрозненную информацию от мапперов и создаёт окончательный, организованный результат:

package com.example;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class InvertedIndexReducer extends Reducer<Text, Text, Text, Text> {
    private Text result = new Text();
    @Override
    protected void reduce(Text key, Iterable