하둡의 상징과도 같은 기술이다. 바로 맵리듀스이다.
사실 하둡만의 상징 같은건 아니고 데이터를 처리하는 솔루션에는 맵리듀스라는개념은 전부 존재한다.
예를들어 DB에도 맵리듀스라는 개념은 존재하는 것이다.
맵리듀스는 무엇인가? 사실 정말 간단한 개념이라서 설명하기 어렵진 않다.
간단히 말하면 맵리듀스는 맵과 리듀스로 나뉘며 맵은 특정 데이터를 가져와서 그걸 키와 밸류의 쌍으로 묶는다.
리듀스는 맵에서 묶은 키와 쌍을 이용해서 내가 필요한 정보로 다시 키와 쌍으로 묶게 된다.
즉 사실상 로직적으로 보면 맵이나 리듀스나 비슷비슷한 작업이다.
차이가 있다면 맵은 raw데이터, 즉 아무데이터나 받아와서 그걸 키와 밸류의 쌍으로 묶는 작업인것이고
리듀스는 이미 키와 밸류로 묶어진 데이터들을 다시 나에게 맞게 키와 밸류로 재조립하는 작업이라고 할 수 있다.
사실 백문이 불여일견이라고 이번예제를 통해서 맵리듀스를 매우 쉽게 이해할 수 있을 것이다.
이번 예제는 WordCount라는 하둡에서 굉장히 유명한 입문 예제이다.
이 예제를 통해서 맵리듀스를 통해서 알아보자.
맵리듀스의 구성
맵리듀스작업을 위해서는 반드시 3가지의 클래스가 필요하다.
이는 각각 맵과 리듀스 그리고 드라이버이다.
앞에서 맵과 리듀스는 설명을 했으므르로 드라이버가 먼놈인지 설명을 해야할거같은데
거창하게 말해서 드라이버지 그냥 자바를 실행시켜줄 main함수가 들은 놈을 드라이버라고 부른다.
맵리듀스의 구성은 정말 심플하지 않은가?
맵,리듀스,드라이버만 있으면 하둡프로그래밍을 할 수 있다고 말 할 수 있다.
이제 아래의 예제는 맵과 리듀스,드라이버를 하나씩 구현해 볼 것이다.
맵
package hadoop;
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class WordCountMapper 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, Mapper<LongWritable, Text, Text, IntWritable>.Context context)
throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
package hadoop;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values,
Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
리듀스 작업은 어찌보면 맵과 같다고 할 수있다. 사실 로직은 동일하다.
맵에서 선별한 데이터들을 가지고 작업을 하게 되는것이다.
리듀스도 총 4개의 인자를 가지는데 맵과 같다 input key,input value,output key,output value이다.
당연히 맵의 output인자와 리듀스의 input인자는 같아야한다.
이 솔루션에서 한가지 특이한점은 reduce에서의 value는 map과는 달리 복수형으로 일종의 컬렉션을 받게된다는 것이다.
즉 하나의 값이 아니라 복수의 값을 받게된다. 그 이유는 key가 중복될 수 있기 때문이다.
리듀스 작업이 맵과 다른점이 있다면 맵은 받는 순서대로 값을 받으므로 input key는 중복될 수 없다.
그러나 리듀스는 맵의 output을 받게되는데 맵의 output은 당연히 리듀스의 input이고
맵의 output의 key는 중복이안된다는 제약은 없다. 따라서 리듀스의 input역시 key는 중복될 수 있다.
리듀스의 실질적은 역활은 중복된 키들을 어떻게 처리하느냐이다.
맵은 의미가 같다고 생각되는 데이터를 key를 묶어서 보낸다. 즉 key를 동일하게 보내는 것이다.
그러면 리듀스에서는 그 동일한 key를 묶어서 처리를 한다고 보면된다.
위의 솔루션에서는 value들은 모두 1로 고정되어있다.
그런데 같은 key의 경우에 sum을 계속해서 더하게 된다는 것이다.
예를들어서 들어가는 단어에서 hello라는 단어가 10개가 들어왔다고 가정해보자.
그러면 <hello,1>이라는 output이 맵에의해 탄생했을것이고 리듀스에서는 저 hello라는 key가 10개가 있으므로
10번 for문을 돌면서 sum의값을 증가시키게 될 것이라는 것이다.
드라이버
package hadoop;
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.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 void main(String[] args) throws Exception {
Configuration conf = new Configuration();
if (args.length != 2) {
System.out.println("Usage: WordCount <input> <output>");
System.exit(2);
;
}
Job job = new Job(conf, "WordCount");
job.setJarByClass(WordCount.class);
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.waitForCompletion(true);
}
}
이제 드라이버로 맵과 리듀스를 등록하고 실행하는 일만남았다.
사실 큰 설명은 필요없다.
중요한건 딴게 아니라 마지막에 job.waitForCompletion(true)를 적어줘야 실행이 된다는 것이다.
나머지는 map과 reduce에 맞게 맞춰주기만하면 끝난다.
실행
일단 실행하려면 넣어야할 텍스트가 필요하다. 단어를 샐거니까.
뭘 써도 상관없다. 필자는 위키피디아에서 HarryPotter항목의 모든 텍스트를 가져왔다.
그걸 메모장에 넣고 input.txt로 저장했다.
hdfs dfs -put input.txt
사용할 텍스트를 하둡파일시스템에 저장하자.
아까 만든 jar파일을 yarn으로 실행시킨다. output결과는 텍스트가아니라 폴더로 나오기에 확장자를 붙혀줄 필요는 없다.
참고로 jar를 어떻게 만드는지 모르는 분은 2장을 참고하라.
제대로 완료됬는지를 확인하자.
hdfs dfs -ls output
hdfs dfs -cat output/part-r-00000
output파일 안에 파일은 part-r-00000로 저장이 된다. 이 파일은 텍스트 파일이므로 cat으로 읽을 수 있다. 읽어보자.
그러면 WordCount가 제대로 실행됬음을 알 수 있다.
'Programming > Java-Hadoop' 카테고리의 다른 글
[Hadoop-02]기초 예제 만들기(SingleFileWirteRead) (2) | 2017.08.26 |
---|