8.ロジックも見ておく
プログラムの構造を理解したうえで、内部で何をやっているか、さらに詳しく理解しましょう。ロジック部分だけを抜き出してみました。
// すべての単語に「1」という数値を付けて、出力する
String line = value.toString(); // ①
StringTokenizer tokenizer = new StringTokenizer(line); // ②
while (tokenizer.hasMoreTokens()) { // ③
word.set(tokenizer.nextToken()); // ④
context.write(word, one); // ⑤
}
// 同じキーに対して、すべてのバリュー(数値が大量に渡ってくる)を足し算して合計する
int sum = 0;
for (IntWritable val : values) { // ⑥
sum += val.get(); // ⑦
}
result.set(sum); // ⑧
context.write(key, result); // ⑨
- Text型からString型へ変換(StringTokenizerで扱うため)
- StringTokenizerを作成(半角スペースで分解するため)
- トークを1つずつ取り出し
- キーとしてText型にワード、
- バリューとしてIntWritableの「1」を出力
- 値を1つずつ取り出し
- 合計値に足し
- IntWritableにセット
- キーと合計値を出力
I am a programmer. I am trying the MapReduce Programming. ↓ Mapper ↓ I,1 am,1 a,1 programmer.,1 I,1 am,1 trying,1 the,1 MapReduce,1 Programming.,1
Mapperの処理で、すべての単語が分解され、1がバリューとして並び、シャッフルに渡ります。シャッフルは、キーでソートし、グルーピングしてくれるので、単語同士が集められ、1が並んでいる状態になるわけです。そして、同じキー同士でReducerのreduceが呼ばれます。Reducerにはキーが1つと、値が配列(Iterable)で渡って来ます。
これで、Reducer処理は終わりです。
1を出力しているのに、個数を数えるのではなく、値を足しているのはなぜでしょうか。これは、Combinerと関係があります。
シャッフル時に、それぞれのMapからシャッフルに渡すレコードがどんどん増えてしまうと、MapReduceはCombinerを呼び出してレコードを減らそうとします。例えば、「the」や「a」のような単語はたくさん出てきます。しかし、Mapper処理がすべて終わらないと、最終的なtheのセットが作れません。待っているとどんどんたまってしまう。そこで、Combineという処理をします。
the,[1,1,1,1] ↓ Combine ↓ the,4
これが何回か繰り返された後に、シャッフル処理が行われて、最終的なReduceには、数値の入った配列が渡される可能性があるのです。
the,[5,7,2,9] ↓ Reducer ↓ the,23
この2つの処理は同じなため、ReducerをCombinerとして登録できるわけです。
日本語・・・
ちょっと蛇足ですが、日本語の処理について言及しておきます。
このサンプルは、半角スペースで分解するようになっているため、英語などのラテン語系の言葉では有効ですが、DBCS文字(日本語、中国語、韓国語等)の文章ではまったく役に立ちません。形態素解析などを事前にやらないと、同様のことができないことは、DBCS文化で暮らす私たちの試練ですね。
整合性
もう1つ蛇足ですが、MapReduceジョブの記述は、同じことを何か所にも宣言しなくてはならず、整合性がとれていなければならないため面倒です。ちょっとしたウィザード等を作って、生成したほうが効率的だと感じました(作ればいいのですが)。
9.おさらい
今回提示したサンプルでは、次のようなことが順次行われています。
- name.yone.comに、ジョブがサブミットされます。
- インプットとして/user/hdpadmin/inpputにあるファイル群が指定されています。
- MyMapper、MyReducerクラスを含んだJARが送信されます。
- TextInputFormatクラスによって、<LongWritable, Text>型で、先頭からのバイト数と各行、というレコードに分解され、量によってはSplit(断片)に分けられ、TaskTrackerへ配布されます。
- TaskTracker上で、レコードごとに、MyMapperクラスのmapメソッドが呼び出されます。MyMapperやmapメソッドの型定義は、レコードの型と一致していなければなりません。
- mapメソッドでは、すべての単語を分解し、キーとしてText型にし、バリューとしてIntWritableの1を付けて出力します。
- <単語,1>という組み合わせのレコード群が、ソートされ、グループ化されます。量が多いとCombine処理としてMyReducerが呼ばれることもあります。
- キーText、バリューIntWritable型のIterableが、キーごとにMyReducerクラスのreduceメソッドに渡されます。
- reduceメソッドでは、渡されたキーと、バリューの合計値(IntWritable)を出力します。
- TextFormatOutputによって、/user/yone/outputに出力ファイルが作成されます。
この中で、1、2、3がMyDriverの処理、6がMyMapperの処理、9がMyReducerの処理です。4、5、7、8、10は、Hadoopフレームワークが処理します。
いかがだったでしょうか。MapReduceのサンプルを理解することで、よりHadoop MapReduceの理解が深まることを祈ります。
