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.



