Running own MapReduce on MacOSX

MaxOSXで自分で書いたMapReduceプログラムを走らせる。

| OS  | HomeBrew | JDK | Hadoop 
|:---|:----|:----|:----|
| MacOSX 10.9.4  | 0.9.5 | 1.8.0 | 2.4.1 | 

## Hadoopのインストール
HadoopはHomebrewで入れたものを使う。

```
$ brew install hadoop
```

インストール先は下記

```
$ cd /usr/local/Celler/hadoop/2.4.1
$ bin/hadoop version
Hadoop 2.4.1
Subversion http://svn.apache.org/repos/asf/hadoop/common -r 1604318
Compiled by jenkins on 2014-06-21T05:43Z
Compiled with protoc 2.5.0
From source with checksum bb7ac0a3c73dc131f4844b873c74b630
This command was run using /usr/local/Cellar/hadoop/2.4.1/libexec/share/hadoop/common/hadoop-common-2.4.1.jar
```

インストールは完了。Stand Aloneか擬似分散モードの設定は[ここ](http://qiita.com/ysk_1031/items/26752b5da1629c9db8f7)を参照。

## 自作MapReduceの作成
[Hadoop Tutorial](http://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html#Example:_WordCount_v1.0)にある通りのWordCountを実装。
MapReduce2はApache Commonsも必要なようだったので今回はMapReduce1で実装。

```java
import java.io.IOException;
import java.util.StringTokenizer;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class WordCount {

  public static class TokenizerMapper
       extends Mapper<Object, Text, Text, IntWritable>{

    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();

    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);
      }
    }
  }

  public static class IntSumReducer
       extends Reducer<Text,IntWritable,Text,IntWritable> {
    private IntWritable result = new IntWritable();

    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);
    }
  }

  public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    Job job = Job.getInstance(conf, "word count");
    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(args[0]));
    FileOutputFormat.setOutputPath(job, new Path(args[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}
```

## コンパイル
上記で作成したファイルを下記に置く。

```
$ mkdir -p workspace/wordcount
$ mv WordCount.java workspace/wordcount/
```

コンパイルを行う

```
$ javac -classpath libexec/share/hadoop/common/hadoop-common-2.4.1.jar:libexec/share/hadoop/mapreduce/hadoop-mapreduce-client-core-2.4.1.jar -d workspace/wordcount workspace/wordcount/WordCount.java
```

Hadoop1.xではhadoop-coreというjarがあるらしいけれど2.xだと見つからなかった。どうやらhadoop-commonとhadoop-mapreduce-client-coreをclasspathに含めればコンパイルできるらしい。でもいちいち何が必要判断するのはめんどくさいのでhadoopのclasspathを与えてやる。

```
$ javac -classpath `bin/hadoop classpath` -d workspace/wordcount workspace/wordcount/WordCount.java
```

jarに固める。

```
$ jar -cvf workspace/wordcount/wordcount.jar -C workspace/wordcount/ .
```

これで必要な実行ファイルができた。これを実行してみる。

## MapReduceの実行
今回は擬似分散モードでYARN上で稼働させる。

### conf

```core-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://localhost:9000</value>
  </property>
</configuration>
```

```hdfs-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
   <property>
     <name>dfs.replication</name>
     <value>1</value>
   </property>
</configuration>
```

```mapred-site.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
  <property>
    <name>mapreduce.framework.name</name>
    <value>yarn</value>
  </property>
</configuration>
```

```yarn-site.xml
<?xml version="1.0"?>
<property>
  <name>yarn.nodemanager.aux-services</name>
  <value>mapreduce_shuffle</value>
</property>
</configuration>
```

### HDFSの起動

```
$ sbin/start-dfs.sh
```

### YARNの起動

```
$ sbin/start-yarn.sh
```

### 確認
各コンポーネントが起動していることを確認する。

```
$ jps
4432 DataNode
4342 NameNode
4539 SecondaryNameNode
4747 NodeManager
4654 ResourceManager
4878 Jps
```

### 入力データの作成

```
$ cat input/word1                                                                                                                                              
a b c d a a b a a a
$ cat input/word2
a a b c c c
```

これらを集計するためHDFSにコピーする。

```
$ bin/hadoop fs -mkdir /hadoop/input
$ bin/hadoop fs -copyFromLocal input/word1 /hadoop/input/word1
$ bin/hadoop fs -copyFromLocal input/word2 /hadoop/input/word2
```

### 実行

```
# 作成したjarファイルから実行対象のクラス名WordCountを指定する
$ bin/hadoop jar workspace/wordcount/wordcount.jar WordCount /hadoop/input /hadoop/output
```

outputディレクトリに集計結果がでているはず。

```
kaisasak% bin/hadoop fs -cat /hadoop/output/part-r-00000
a       8
b       3
c       4
d       1
```