S-JIS[2010-02-21/2010-02-22] 変更履歴

Hadoopチュートリアル WordCount

Hadoop公式ページの「Map/Reduce チュートリアル」のWordCountを単独環境で動かしてみる。


Eclipseの設定

チュートリアルのページではjavacコマンドを使ってコンパイルしているが、やはりコーディングにはEclipseを使いたい。
Hadoopのjarファイルをビルドパスに追加するだけでよい。

  jarファイル 備考
Hadoop C:\cygwin\usr\local\hadoop-0.20.1\hadoop-0.20.1-core.jar ↓ソースの場所(Eclipseで添付可能)
C:/cygwin/usr/local/hadoop-0.20.1/src

チュートリアルのソース

チュートリアルのソースはHadoop0.20.1より前のバージョンのものらしく、0.20.1だとコンパイルが警告になる(警告になるだけで、実行は出来る)。
なので、(正しいかどうか分からないけど^^;)0.20.1用に直してみた。

// http://oss.infoscience.co.jp/hadoop/common/docs/current/mapred_tutorial.html 2010-02-21
package jp.hishidama.hadoop.tutorial;

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.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 {

	public static class Map extends Mapper<LongWritable, Text, Text, IntWritable> {
		private final static IntWritable one = new IntWritable(1);
		private Text word = new Text();

		@Override
		protected 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);
			}
		}
	}

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

		@Override
		protected 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 {
		Configuration conf = new Configuration();
		Job job = new Job(conf, "wordcount");
		job.setJarByClass(WordCount.class);

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

		job.setMapperClass(Map.class);
		job.setCombinerClass(Reduce.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]));

		if (true) {
			// 実行を開始して完了するまで待つ
			boolean success = job.waitForCompletion(true);
			System.out.println(success);
		} else {
			// 実行を開始して(完了を待たずに)すぐ戻ってくる
			job.submit();

//			while (!job.isComplete());
//			boolean success = job.isSuccessful();
//			System.out.println(success);
		}
	}
}

変更点は以下の通り。

変更箇所 変更内容
Mapクラス 元は「extends MapReduceBase implements Mapper」だった。
しかしMapReduceBaseが非推奨になっていたので、
mapreduceパッケージのMapperクラスを継承するよう変更。
(mapredパッケージのMapperインターフェースとは別もの)
map()メソッド Mapクラスの継承元が変わったので、オーバーライドするメソッドも変わった。
特に第3引数以降がcontextに変更になっている。
したがって「output.collect(ket, value)」→「context.write(key, value)」になった。

Reduceクラス

元は「extends MapReduceBase implements Reducer」だった。
しかしMapReduceBaseが非推奨になっていたので、
mapreduceパッケージのReducerクラスを継承するよう変更。
(mapredパッケージのReducerインターフェースとは別もの)
reduce()メソッド Reduceクラスの継承元が変わったので、オーバーライドするメソッドも変わった。
第2引数がIteratorからIterableに変わった。
したがってループ方法はiteratorでなくfor each構文が使える。
また、第3引数以降がcontextになっている。
したがって「output.collect(ket, value)」→「context.write(key, value)」になった。
Configuration
Job
コンフィグは、元はJobConfだったが非推奨になっていた。
そこでConfigurationクラスとJobクラスに変更。
Configurationには設定ファイル(xmlファイル)をaddResouce()で指定できるようだ。
Jobのコンストラクト方法はJobConfとちょっと違う。
JobConfに値をセットしていたメソッドはJobでほぼそのまま使える。
ただ、setInputFormat()・setOutputFormat()は
setInputFormatClass()・setOutputFormatClass()になっている模様。
FileInputFormat
FileOutputFormat
FileInputFormat.setInputPaths()・FileOutputFormat.setOutputPath()は
クラス名・メソッド名は変わっていない。
が、第1引数がJobConfからJobになったので、そのままでは使えない。
mapredパッケージのFileInputFormat・FileOutputFormatでなく、
mapreduce.input.FileInputFormat・mapreduce.output.FileOutputFormatを使う。
実行方法 JobClient.runJob()でなく、Jobを使った実行方法に変更。

CDH4用のWordCount


チュートリアルのコンパイル

Hadoop用のプログラムを実行するには(Cygwinから)hadoopコマンドで実行する必要があるので、Eclipseから直接実行することは出来ない。

Eclipseを使っていればコンパイルは自動的に行われるので問題ないが、
hadoopコマンドで実行するにはjarファイルでないといけないので、jarファイル化する必要がある。
何度も作り直すことを考えるならbuild.xml化した方が断然楽だが、Eclipseのエクスポート機能を使ってjarファイルを作ることも出来る。

とりあえずチュートリアルを実行する為に「C:\cygwin\home\hadoop\tutorial\」というディレクトリーを作り、
そこに「wordcount.jar」を置くことにする。

$ mkdir -p /home/hadoop/tutorial

チュートリアルの実行

Cygwinのbashから)HADOOP_HOME/bin/hadoopコマンドを使って実行する。

データの準備(単独環境用)

cd /home/hadoop/tutorial
mkdir input
echo "Hello World Bye World" > input/file01
echo "Hello Hadoop Goodbye Hadoop" > input/file02

再実行する際には出力先のoutputディレクトリーが存在しているとエラーになるので、削除する。
(存在したまま実行すると、FileAlreadyExistsExceptionが発生する)

cd /home/hadoop/tutorial
rm -r output

実行

cd /home/hadoop/tutorial
/usr/local/hadoop/bin/hadoop jar wordcount.jar jp.hishidama.hadoop.tutorial.WordCount ./input ./output

この引数input・outputは、ディレクトリー名を相対パスで表している。input→args[0]、output→args[1]

Cygwinで実行すると以下のような警告が出る(ことがある)が、気にしなーい。

cygwin warning:
  MS-DOS style path detected: C:\cygwin\usr\local\hadoop-0.20.1\/build/native
  Preferred POSIX equivalent is: /usr/local/hadoop-0.20.1/build/native
  CYGWIN environment variable option "nodosfilewarning" turns off this warning.
  Consult the user's guide for more details about POSIX paths:
    http://cygwin.com/cygwin-ug-net/using.html#using-pathnames

結果確認

$ cat output/*
Bye     1
Goodbye 1
Hadoop  2
Hello   2
World   2

Hadoop単独環境実行用build.xml

Eclipseでソースを直してjarファイルを作ってCygwinでoutputディレクトリーを消してから実行
って面倒なので、Antのbuild.xmlを書いてみた。

<?xml version="1.0" encoding="Windows-31J"?>
<project name="hadoop standalone" basedir="." default="mk_jar">

	<property name="cygwin"        location="C:\cygwin" />
	<property name="hadoop.home"   location="${cygwin}/usr/local/hadoop-0.20.1" />

	<property name="target.home"   location="${cygwin}/home/hadoop/tutorial" />
	<property name="target.input"  location="${target.home}/input" />
	<property name="target.output" location="${target.home}/output" />

	<property name="jar.file"      location="${target.home}/wordcount.jar" />
	<property name="main.class" value="jp.hishidama.hadoop.tutorial.WordCount" />

	<target name="init" description="ディレクトリーや入力ファイルを準備する">
		<mkdir dir="${target.input}" />
		<echo file="${target.input}/file01">Hello World Bye World</echo>
		<echo file="${target.input}/file02">Hello Hadoop Goodbye Hadoop</echo>
	</target>

	<target name="mk_jar" description="jarファイルを作成する">
		<jar destfile="${jar.file}">
			<fileset dir="../classes" />
		</jar>
	</target>

	<target name="rm_output" description="outputディレクトリーを削除する">
		<delete dir="${target.output}" />
	</target>

	<target name="execute_jar" description="hadoopを実行する">
		<exec executable="${cygwin}/bin/bash.exe">
			<arg value="--login" />
			<arg value="/usr/local/hadoop/bin/hadoop" />
			<arg value="jar" />
			<arg value="${jar.file}" />
			<arg value="${main.class}" />
			<arg value="${target.input}" />
			<arg value="${target.output}" />
		</exec>
	</target>

	<target name="cat_output" description="outputの内容を表示する">
		<exec executable="cmd" dir="${target.output}">
			<arg value="/c" />
			<arg value="type" />
			<arg value="*." />
		</exec>
	</target>

	<target name="execute_all" depends="rm_output,execute_jar,cat_output" description="実行して結果を表示する" />
	<target name="all" depends="mk_jar,execute_all" description="jarファイルを作って実行する" />
</project>

このbuild.xmlは、Eclipseのプロジェクトにbinというディレクトリーを作り、そこに置く想定。
生成されるjarファイルやinput・outputは${target.home}で指定された場所に置かれる想定。
EclipseからAntを実行する方法

  1. initターゲットで初期化を行う。(jarファイルを置くディレクトリーや入力ファイルを作成する)
    最初に一度だけ実行すればOK。
  2. mk_jarターゲットでjarファイルを作成する。
  3. hadoopを再実行する場合はoutputディレクトリーを削除する為にrm_outputターゲットを実行しておく。
  4. execute_jarターゲットでhadoopを実行する。
  5. 実行結果を表示したい場合はcat_outputターゲットを実行する。

execute_allターゲットでoutputの削除→hadoopの実行→結果の表示を行う。
allターゲットはjarファイルを作ってからexecute_allを行う。


hadoopコマンドはUNIX(Cygwin)上で動くからexecタスクの引数はUNIX形式のパス指定にしないといけないのかと思ったら、
逆にWindowsのパスでないと駄目なようだ。
(実行用のHADOOP_HOME/bin/hadoopシェル自体がCygwinに対応していて、クラスパス類はWindows用に変換してくれる模様)


Eclipseからデバッグ実行

HADOOP_HOME/bin/hadoopシェルの中を見てみると、結局はjavaコマンドでクラスを実行しているだけ。[2010-02-22]
ということは、パラメーターさえ用意してやれば、Eclipseから直接実行できる。

  1. WordCountをとりあえず「Javaアプリケーション」として実行する。(何らかの例外は出るが、WordCountの実行構成が作られる)
  2. メニューバーの「実行(R)」→「実行構成(N)」で「実行構成」ダイアログを開く。
  3. 左側のツリーからWordCountを選択する。
  4. 右側のタブから色々設定する。
    タブ 項目 設定値 備考
    メイン メイン・クラス(M) jp.hishidama.hadoop.tutorial.WordCount 変更不要。
    引数 プログラムの引数(A) C:\cygwin\home\hadoop\tutorial\input
    C:\cygwin\home\hadoop\tutorial\output
     
    WordCountの引数。
    VM引数(G) -Xmx1000m
    -Dhadoop.log.dir=C:\cygwin\usr\local\hadoop-0.20.1\logs
    -Dhadoop.log.file=hadoop.log
    -Dhadoop.home.dir=C:\cygwin\usr\local\hadoop-0.20.1\
    -Dhadoop.id.str=host
    -Dhadoop.root.logger=INFO,console
    -Dhadoop.policy.file=hadoop-policy.xml
     
     
    クラスパス クラスパス(L) C:\cygwin\usr\local\hadoop-0.20.1\conf
    (これは無くても動くが、一番先頭に追加することで
    conf\log4j.propertiesの設定が読み込まれるようになる)
    ユーザー・エントリーに
    「拡張(A)」→「外部フォルダー(E)」で
    左記のものを追加。
    C:\cygwin\usr\local\hadoop-0.20.1\hadoop-0.20.1-core.jar コンパイルに使っているので
    デフォルトで入っているはず。
    C:\cygwin\usr\local\hadoop-0.20.1\lib
    の直下のjarファイル全て
    (場合によってはlib\jsp-2.1内のjarファイルも全て)
    ユーザー・エントリーに
    「外部JARの追加(X)」で
    左記のものを追加。
    環境 変数 PATH C:\cygwin\bin 「新規(E)」で名前「PATH」を追加。
    (これが無いとchmodが実行できない)
  5. 実行する。

この方法でデバッグ実行をすれば、ブレークポイントで止めてデバッグすることも出来る。


Hadoop目次へ戻る / Java目次へ行く / 技術メモへ戻る
メールの送信先:ひしだま