30分で実行するAmazon Elastic MapReduce(Amazon EMR)

今年のテーマの一つはビッグデータということで、そろそろ本格的にMapReduceに手を出そうと思います。

手軽にMapReduceを試してみるのであれば、やはりAWSでしょう。今回はAmazon Elastic MapReduce(Amazon EMR)を動かす方法を簡単にまとめておきます。

実行する処理の例としては、Hadoopのチュートリアルにある処理をそのまま使わせてもらいましょう。与えられたテキスト(英文)に出現する単語を数えるというものです。

実行する処理
与えられたテキストに出現する単語を数える

実行の手順

Amazon EMRにはApache Hadoopが使われています。本来Hadoopを使うためには、Hadoopの環境そのものをセットアップする必要があるのですが、そこをAWSが既にやってくれているわけです。

そのため、手順としては

  1. Hadoopのジョブとなるjarファイルを作る
  2. そのjarファイルと入力ファイルをAmazon S3へアップロードする
  3. Amazon EMRのコンソールから処理を実行する

となります。

ジョブのjarファイルを作る

まずはmavenでプロジェクトを作成しましょう。

執筆時点でサポートされているHadoopのバージョンは0.20.2ですので、該当するdependencyを追加します。

<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-core</artifactId>
  <version>0.20.205.0</version>
</dependency>

次に必要なクラスを実装していきます。

  • エントリーポイントとなる、mainメソッドをもつクラス
  • Mapperクラス
  • Reducerクラス

この3つのクラスが必要になりますが、今回は1つのソースファイルに納めてしまうことにしましょう。コードは以下のようになります。内容はチュートリアルのサンプルコードほぼそのままです。

package hadooptest;

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

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
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.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;

public class WordCount {

	/**
	 * Mapper
	 */
	public static class Map
			extends Mapper<LongWritable, Text, Text, IntWritable> {

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

		@Override
		public void map(
				LongWritable key,
				Text value,
				Context context) throws IOException, InterruptedException {

			String line = value.toString();
			StringTokenizer tokenizer = new StringTokenizer(line);
			while (tokenizer.hasMoreTokens()) {
				word.set(tokenizer.nextToken());
				context.write(word, one);
			}
		}
	}

	/**
	 * Reducer
	 */
	public static class Reduce
			extends Reducer<Text, IntWritable, Text, IntWritable> {

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

	public static void main(String[] args) throws Exception {
		Job job = new Job();
		job.setJarByClass(WordCount.class);
		job.setJobName("wordcount");

		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);

		job.setMapperClass(Map.class);
		job.setReducerClass(Reduce.class);

		job.setInputFormatClass(TextInputFormat.class);
		job.setOutputFormatClass(TextOutputFormat.class);

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

		job.waitForCompletion(true);
	}
}

なお、hadoop-0.20.2はAPIの移行期にあり、旧APIはorg.apache.hadoop.mapredパッケージ以下に、新APIはorg.apache.hadoop.mapreduceパッケージ以下にあります。似たような名前のクラスが多いので注意してください。

またmapとreduceの実装メソッドには、必ず@Overrideアノテーションを付けておくことをお勧めします。なぜならMapperクラス・Reducerクラスとも具象クラスであり、メソッドの実装を強制されないためです。(それはシグネチャが間違っていても、コンパイルエラーにならないことを意味します)

これをmavenでjarにしておきましょう。

ファイルをAmazon S3へアップロードする

処理の入力となるテキストファイルを作成します。なんでも構わないのですが、今回はこんなテキストを用意しました。

Now the serpent was more subtil than any beast of the field which the LORD God had made. And he said unto the woman, Yea, hath God said, Ye shall not eat of every tree of the garden?
And the woman said unto the serpent, We may eat of the fruit of the trees of the garden:
But of the fruit of the tree which is in the midst of the garden, God hath said, Ye shall not eat of it, neither shall ye touch it, lest ye die.
And the serpent said unto the woman, Ye shall not surely die:
For God doth know that in the day ye eat thereof, then your eyes shall be opened, and ye shall be as gods, knowing good and evil.
And when the woman saw that the tree was good for food, and that it was pleasant to the eyes, and a tree to be desired to make one wise, she took of the fruit thereof, and did eat, and gave also unto her husband with her; and he did eat.
And the eyes of them both were opened, and they knew that they were naked; and they sewed fig leaves together, and made themselves aprons.

AWSのWebコンソールにログインして、S3のタブを開きます。

次にEMR用に新しくバケットを作成し、更にバケットの中にフォルダ”input”、”jar”を作成します。そのフォルダにそれぞれ入力ファイル、jarファイルをアップロードします。

これで実行の準備ができました。

処理を実行する

AWSのWebコンソールで、Elastic MapReduceのタブを開きます。

ジョブフローの作成ボタンを押し、ウィザードを開きます。いくらか細かい設定もできるのですが、ここでは大切なところだけを挙げます。

  • Job Typeでは”Custom JAR”を選択します
  • JAR Locationは、s3://<backet-name>/jar/<jarfile-name> とします
  • JAR Argumentsでは、以下のように引数を3つ与えます
    • hadooptest.WordCount ・・・mainメソッドをもつクラス名
    • s3://<backet-name>/input ・・・入力ファイルのを置いたフォルダ
    • s3://<backet-name>/output ・・・結果を出力するフォルダ
  • Keep Aliveは”No”を選択します(処理が終了したらEC2インスタンスを破棄します)

出力フォルダが既に存在しているとエラーになりますので注意してください。

ウィザードを完了すると、ジョブフロー一覧画面に今作成したジョブフローが表示されます。処理が完了するまでしばらく待ちましょう。

結果

処理が完了すると、状態のカラムが”COMPLETED”になります。(失敗した場合は”FAILED”)Amazon S3の指定したフォルダ内に結果が出力されているはずですので、確認してみましょう。

以下実行結果の一部です。ちゃんと単語が数えられていますね。

And	5
But	1
LORD	1
Now	1
We	1
also	1
as	1
beast	1
desired	1
did	2
doth	1
eat	4
eyes,	1
fruit	3
garden,	1

これでMapReduceを使った処理が実行できました。

コメントを残す