Hadoop学习感悟(一)
Hadoop的安装
Hadoop在Linux下安装相对较为简单。具体可参考网上的安装教程,也可直接到Apache网站上找到安装指南。这里需要注意的是找对Hadoop的版本和对应的安装教程
例如Hadoop 2.5.2的Apache文档可以直接到地址找到安装教程(个人感觉Apache的安装教程较为清楚,所以如果有条件还是按照官网的来吧)。
吐槽-,-
可以说对Map-Reduce的理解是我花费时间较长的一部分。主要是学习过程中遇到的各种坑(自己挖的和别人挖的)。先说说客观原因。当初手贱买了本《Hadoop应用开发技术详解》@刘刚 。个人不推荐这本书,整本书的章节安排、语言、代码都比较差劲。可以说是作者本人从开始学习计算机到现在读到的最差的书(没有之一)。然后就是自己挖的坑,刚开始跟着书写的WordCount小程序,由于自己改了一行代码,导致结果全是1。然后是对Map-Reduce过程中的Shuffle过程理解,翻了好几天的博客都没找到我想要的,这些在后面会详细说。
WordCount小程序
WordCount在Map-Reduce框架里应该算是我们刚学习语言的Hello ,world了吧。本人写的WordCount程序分为三个类,分别是Mapper , Reducer , Main , 代码如下:
WordMapper
public class WordMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private static IntWritable one = new IntWritable(1);
Text word = new Text();
public void map(LongWritable ikey, Text ivalue, Context context)
throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(ivalue.toString());
while(itr.hasMoreTokens()){
word.set(itr.nextToken());
context.write(word, one);
}
}
}
WordReducer
public class WordReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text _key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
// process values
int sum = 0;
for (IntWritable val : values) {
sum = sum + 1; //sum += val.get()
}
result.set(sum);
context.write(_key, result);
}
}
Main
public class Main {
public static void main(String[] args) {
Configuration conf = new Configuration();
try {
GenericOptionsParser parser = new GenericOptionsParser(conf, args);
args = parser.getRemainingArgs();
Job job = Job.getInstance(conf , "wordcount");
job.setJarByClass(Main.class);
job.setMapperClass(WordMapper.class);
job.setReducerClass(WordReducer.class);
job.setCombinerClass(WordReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.submit();
System.exit(job.waitForCompletion(true) ? 0 : 1);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在Reducer的实现里,我的想法是,既然map任务的输出是每个key的value都是1,那直接加1与val.get()的结果是一样的。所以自己改为了sum+=1。。。最后的结果是每个key的统计都是1。
分析
由于是刚刚接触这个框架(之前只是了解大体的流程),所以开始了漫无目的的查找(这个问题真心不知道怎么在搜索引擎上搜)。后来自己查看hadoop的userlog,看到输出分为了三个部分,结合对Map-Reduce的一点理解,分别是Map,Combine,Reduce三个过程的输出。后来把Main类中main方法里的setCombinerClass这句注释掉后,一切正常。
由Combine过程引发的思考
由于看到了Combine过程,自然要好好了解下,在看了许多博客后,总结如下:
Map过程会将临时结果写到内存缓冲区中,当内存缓冲区占用达到一定百分比(默认80%,后面以缓冲区100MB为例)后,会启动溢写进程spill到磁盘中去(此阶段会对key进行排序),将这80%写入到磁盘文件中去,剩余的20MB可以继续写。
如果map的输出较多,会产生较多的临时文件,最终会Merge成一个文件(网上的说法)。
这里我的疑问是:每个map的输出都是不同的,上面说的Merge成一个文件是所有map的输出都Merge到一起还是每个map的输出merge到一起,然后由Recuder去取?