Os Segredos Místicos da Ordenação no Hadoop

HadoopBeginner
Pratique Agora

Introdução

Num misterioso mercado noturno, uma figura cativante adornada com uma máscara ornamentada move-se graciosamente pela multidão agitada. Este enigmático dançarino mascarado parece possuir um poder secreto, organizando sem esforço as barracas caóticas em uma disposição ordenada a cada giro e balanço. Seu objetivo é desvendar o mistério por trás desse talento notável, dominando a arte do Hadoop Shuffle Comparable.

Implementar o Mapper

Nesta etapa, criaremos uma classe Mapper personalizada para processar os dados de entrada e emitir pares chave-valor. A chave será uma chave composta, compreendendo dois campos: o primeiro caractere de cada palavra e o comprimento da palavra. O valor será a própria palavra.

Primeiro, altere o usuário para hadoop e, em seguida, mude para o diretório home do usuário hadoop:

su - hadoop

Em seguida, crie um arquivo Java para a classe Mapper:

touch /home/hadoop/WordLengthMapper.java

Adicione o seguinte código ao arquivo WordLengthMapper.java:

import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class WordLengthMapper extends Mapper<LongWritable, Text, CompositeKey, Text> {

    private CompositeKey compositeKey = new CompositeKey();

    public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] words = line.split("\\s+");

        for (String word : words) {
            compositeKey.setFirstChar(word.charAt(0));
            compositeKey.setLength(word.length());
            context.write(compositeKey, new Text(word));
        }
    }
}

No código acima, criamos uma classe WordLengthMapper que estende a classe Mapper da API Hadoop MapReduce. O método map recebe uma chave LongWritable (representando o deslocamento de bytes da linha de entrada) e um valor Text (a própria linha de entrada). Em seguida, ele divide a linha de entrada em palavras individuais, cria um objeto CompositeKey para cada palavra (contendo o primeiro caractere e o comprimento da palavra) e emite o CompositeKey como a chave e a palavra como o valor.

Implementar a CompositeKey

Nesta etapa, criaremos uma classe CompositeKey personalizada que implementa a interface WritableComparable da API Hadoop MapReduce. Esta classe será usada como a chave em nosso trabalho MapReduce, permitindo que classifiquemos e agrupemos os dados com base no primeiro caractere e no comprimento de cada palavra.

Primeiro, crie um arquivo Java para a classe CompositeKey:

touch /home/hadoop/CompositeKey.java

Em seguida, adicione o seguinte código ao arquivo CompositeKey.java:

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;

public class CompositeKey implements WritableComparable<CompositeKey> {

    private char firstChar;
    private int length;

    public CompositeKey() {
    }

    public void setFirstChar(char firstChar) {
        this.firstChar = firstChar;
    }

    public char getFirstChar() {
        return firstChar;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public int getLength() {
        return length;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeChar(firstChar);
        out.writeInt(length);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        firstChar = in.readChar();
        length = in.readInt();
    }

    @Override
    public int compareTo(CompositeKey other) {
        int cmp = Character.compare(firstChar, other.firstChar);
        if (cmp != 0) {
            return cmp;
        }
        return Integer.compare(length, other.length);
    }

    @Override
    public int hashCode() {
        return firstChar + length;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof CompositeKey) {
            CompositeKey other = (CompositeKey) obj;
            return firstChar == other.firstChar && length == other.length;
        }
        return false;
    }

    @Override
    public String toString() {
        return firstChar + ":" + length;
    }
}

No código acima, criamos uma classe CompositeKey que implementa a interface WritableComparable. Ela possui dois campos: firstChar (o primeiro caractere de uma palavra) e length (o comprimento da palavra). A classe fornece métodos getter e setter para esses campos, bem como implementações dos métodos write, readFields, compareTo, hashCode, equals e toString exigidos pela interface WritableComparable.

O método compareTo é particularmente importante, pois define como as chaves serão classificadas no trabalho MapReduce. Em nossa implementação, primeiro comparamos os campos firstChar das duas chaves. Se forem diferentes, retornamos o resultado dessa comparação. Se os campos firstChar forem os mesmos, comparamos os campos length.

Implementar o Reducer

Nesta etapa, criaremos uma classe Reducer personalizada para processar os pares chave-valor emitidos pelo Mapper e gerar a saída final.

Primeiro, crie um arquivo Java para a classe Reducer:

touch /home/hadoop/WordLengthReducer.java

Em seguida, adicione o seguinte código ao arquivo WordLengthReducer.java:

import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class WordLengthReducer extends Reducer<CompositeKey, Text, CompositeKey, Text> {

    public void reduce(CompositeKey key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        StringBuilder sb = new StringBuilder();
        for (Text value : values) {
            sb.append(value.toString()).append(", ");
        }
        sb.setLength(sb.length() - 2);
        context.write(key, new Text(sb.toString()));
    }
}

No código acima, criamos uma classe WordLengthReducer que estende a classe Reducer da API Hadoop MapReduce. O método reduce recebe uma chave CompositeKey (contendo o primeiro caractere e o comprimento de uma palavra) e um Iterable de valores Text (as palavras que correspondem à chave).

Dentro do método reduce, concatenamos todas as palavras que correspondem à chave em uma string separada por vírgulas. Usamos um StringBuilder para construir eficientemente a string de saída e removemos a vírgula e o espaço à direita antes de escrever o par chave-valor na saída.

Implementar o Driver

Nesta etapa, criaremos uma classe Driver para configurar e executar o trabalho MapReduce.

Primeiro, crie um arquivo Java para a classe Driver:

touch /home/hadoop/WordLengthDriver.java

Em seguida, adicione o seguinte código ao arquivo WordLengthDriver.java:

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 WordLengthDriver {

    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            System.err.println("Usage: WordLengthDriver <input> <output>");
            System.exit(1);
        }

        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "Word Length");

        job.setJarByClass(WordLengthDriver.class);
        job.setMapperClass(WordLengthMapper.class);
        job.setReducerClass(WordLengthReducer.class);
        job.setOutputKeyClass(CompositeKey.class);
        job.setOutputValueClass(Text.class);

        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

No código acima, criamos uma classe WordLengthDriver que serve como o ponto de entrada para nosso trabalho MapReduce. O método main recebe dois argumentos de linha de comando: o caminho de entrada e o caminho de saída para o trabalho.

Dentro do método main, criamos um novo objeto Configuration e um novo objeto Job. Configuramos o trabalho definindo as classes mapper e reducer, as classes de chave e valor de saída e os caminhos de entrada e saída.

Finalmente, enviamos o trabalho e aguardamos sua conclusão. Se o trabalho for concluído com sucesso, saímos com um código de status 0; caso contrário, saímos com um código de status 1.

Para executar o trabalho, você pode usar o seguinte comando:

javac -source 8 -target 8 -classpath "/home/hadoop/:/home/hadoop/hadoop/share/hadoop/common/hadoop-common-3.3.6.jar:/home/hadoop/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-client-core-3.3.6.jar:/home/hadoop/hadoop/share/hadoop/common/lib/*" -d /home/hadoop /home/hadoop/WordLengthMapper.java /home/hadoop/CompositeKey.java /home/hadoop/WordLengthReducer.java /home/hadoop/WordLengthDriver.java
jar cvf word-length.jar *.class
hadoop jar word-length.jar WordLengthDriver /input /output

Finalmente, podemos verificar os resultados executando o seguinte comando:

hadoop fs -cat /output/*

Exemplo de saída:

A:3 Amr
A:6 AADzCv
A:10 AlGyQumgIl
...
h:7 hgQUIhA
h:8 hyrjMGbY, hSElGKux
h:10 hmfHJjCkwB
...
z:6 zkpRCN
z:8 zfMHRbtk
z:9 zXyUuLHma

Resumo

Neste laboratório, exploramos o conceito de Hadoop Shuffle Comparable implementando um trabalho MapReduce que agrupa palavras com base em seu primeiro caractere e comprimento. Criamos um Mapper personalizado para emitir pares chave-valor com uma chave composta, uma classe CompositeKey personalizada que implementa a interface WritableComparable, um Reducer para concatenar palavras com a mesma chave e uma classe Driver para configurar e executar o trabalho.

Através deste laboratório, obtive uma compreensão mais profunda do framework Hadoop MapReduce e da importância de tipos de dados personalizados e classificação em computação distribuída. Ao dominar o Hadoop Shuffle Comparable, podemos projetar algoritmos eficientes para processamento e análise de dados, liberando o poder de big data como o enigmático dançarino de máscara organizando as barracas caóticas do mercado noturno.