WordCount

Exemplo de Programação Java - MapReduce 
__________________________________________________

Problema: Contar a frequência das palavras constantes dos arquivos de entrada: sample1.txt e sample2.txt


Workflow

Mapper: divide cada registro (linha) em palavras
input: <offset, linha>
output: <palavra, 1>
Reducer: soma a contagem feita para cada palavra
input: <palavra, (1, 1, ...)>
output: <palavra, contagem>

Compile o arquivo WordCount.java e crie o .jar (arquivo java interpretável - executável do java):
$ javac -d . -cp `hadoop classpath` WordCount.java
$ jar cf wc.jar WordCount*.class

Assumindo que o diretório /user/emilia/wordcount/input é o diretório de input no HDFS.

Copie os arquivos de entrada do programa para o diretório do hdfs:

Verifique se a cópia foi feita com sucesso:
$ hdfs dfs -ls /user/emilia/wordcount/input/

/user/emilia/wordcount/input/sample1.txt
/user/emilia/wordcount/input/sample2.txt

Execute o programa:
$ hadoop jar wc.jar WordCount emilia

Verifique a saída do programa:
$ hdfs dfs -cat /user/emilia/wordcount/output/part-r-00000

Walk-through

A aplicação WordCount é direta e simples.

MAP

public void map(Object key, Text value, Context context)
                throws IOException, InterruptedException 
{
  StringTokenizer itr = new StringTokenizer(value.toString());
  while (itr.hasMoreTokens()) {
    word.set(itr.nextToken());
    context.write(word, one);
  }
}
A implementação do Mapper, via método map, processa uma linha de cada vez.
Ele então divide a linha em tokens separados por whitespaces, utilizando o tipo StringTokenizer, e emite um par key-value < <palavra>, 1>.

O output do primeiro arquivo o map é:
< I, 1>
< love, 1>
< Big, 1>
< Data, 1>
< You, 1>
< love, 1>
< it, 1>
< too, 1>
< Best, 1>
< Regards, 1>

O output do segundo arquivo o map é:
< I, 1>
< love, 1>
< Hello, 1>
< World, 1>
< You, 1>
< love, 1>
< it, 1>
< too, 1>
< Kind, 1>
< Regards, 1>


COMBINER

O programa WordCount especifica um Combiner na fase de Map: job.setCombinerClass(IntSumReducer.class)
Portanto, a saída de cada Map é passada pelo combiner local (que utiliza o mesmo que o Reducer na configuração do job) para agregação local, após ser ordenada pelas chaves.

public void reduce(Text key, Iterable<IntWritable> values,
                   Context context) throws IOException, InterruptedException 
{
  int sum = 0;
  for (IntWritable val : values) {
    sum += val.get();
  }
  result.set(sum);
  context.write(key, result);
}

O output do Combiner para o primeiro arquivo é:
< Best, 1>
< Big, 1>
< Data, 1>
< I, 1>
< it, 1>
< love, 2>
< Regards, 1>
< too, 1>
< You, 1>


REDUCE

A implementação do Reducer, via método reduce, soma os valores que são cada ocorrência de palavras (keys) contadas.

 public void reduce(Text key, Iterable<IntWritable> values,
                       Context context)
                       throws IOException, InterruptedException 
{
      int sum = 0;
      for (IntWritable val : values) {
        sum += val.get();
      }
      result.set(sum);
      context.write(key, result);
    }
 }


O método main especifica várias características do job, tais como os caminhos de input/output, tipos do par key/value, formatos de input/output etc. 
Ele chama a função job.waitForCompletion para submeter o job e monitorar o seu progresso.


public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    Job job = Job.getInstance(conf, "WordCount");
    job.setJarByClass(WordCount.class);
    job.setMapperClass(TokenizerMapper.class);
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    FileInputFormat.addInputPath(job, new Path("/user/" + args[0] + "/wordcount/input"));
    FileOutputFormat.setOutputPath(job, new Path("/user/" + args[0] + "/wordcount/output"));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }

O  output final do programa é:
Best    1
Big     1
Data    1
Hello   1
I       2
Kind    1
Regards 2
World   1
You     2
love    4
too     2