浅略理解MapReduce的概念机制是开始真正使用Hadoop开发Mapreduce程序的第一步,是一个充分条件。理解和实践并进才能让更多的问题暴露对理论的理解的不够。继续学习《Hadoop基础教程》。
1.Map与Reduce
Hadoop将数据分成不小于64MB的块,因此每个数据块都有一个对应的键,而数据块就作为值,由此形成键值对,就是所说的Map,映射。Reduce将Map输出的键值对进行汇集和缩减。有一个清晰的描述:
{K1,V1}−>{K2,List<V2>}−>{K3,V3}
第一个键值对是Map的输入,Map的输出是第二个键值对所表示的有一个键及对应的值列表组成,其形成的过程是一个叫shuffle的方法。第三个键值对由Reduce缩减输出,也是MapReduce的最终输出。
2.MapReduce的Java API
实践出真知,先贴上总结的示例,再逐步分析。
import org.apache.hadoop.conf.*;
import org.apache.commons.io.IOExceptionWithCause;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
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.output.FileOutputFormat;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import java.io.IOException;
import java.io.InterruptedIOException;
public class WordCount {
public static class WordCountMapper extends Mapper<Object,Text,Text,IntWritable> {
private final static IntWritable one =new IntWritable(1);
private Text word=new Text();
public void map(Object key,Text value,Context context) {
String words[]=value.toString().split("\\W");
try {
for (String str : words) {
word.set(str);
context.write(word, one);
}
}catch (IOException e) {
e.printStackTrace();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class WordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
public void reduce(Text key, Iterable<IntWritable> values, Context context) {
int total = 0;
for (IntWritable val : values) {
total+=val.get();
}
try {
context.write(key, new IntWritable(total));
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String...args)throws Exception {
Configuration conf=new Configuration();
Job job=new Job(conf,"word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setCombinerClass(WordCountReducer.class);
FileInputFormat.addInputPath(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
System.exit(job.waitForCompletion(true)?0:1);
}
}
首先说明一下用的IDE是IntelliJ,要在intelliJ上写MapReduce要添加Hadoop的依赖。以防以后忘记,步骤如下:
1.在project Struct下的Modules中添加MapReduce所需要的jar包
2.用的包有
$ hadoop-2.6.0/share/hadoop/mapreduce
$ hadoop-2.6.0/share/hadoop/common
$ hadoop-2.6.0/share/hadoop/common/lib
这三个目录下的jar包。
3.制作好modules后再进入artifacts将Modeles打包成jar包即可
这样我们就完成了MapReduce程序开发的前期准备,我们所梦寐以求的API就都有啦,终于可以写MapReduce程序啦!(好吃力的样子)。
我们自定义的Mapper类和Reducer类都要继承Hadoop的Mapper和Reducer类,分别需要重载实现
void map(K1 key,V1 value,Mapper.Context context) //Context提供与Hadoop框架通信的基本机制
throws IOException,InterruptedException
{
//TODO
}
和
void reduce(K1 key,Iterable<V2> values,Reducer.Context context)
throws IOException,InterruptedException
{
//TODO
}
map:
在WordCount(字数统计),TextInputFormat提供了以行号为键的映射,map由此形成键值对。
context.write(word, one);语句负责将map方法中的键值对进行输出。
reduce:
负责对map形成的键和一组值的组合进行字数统计。
Map的输入:{IntWritable,Text}
输出:{Text,IntWritable}
Reduce的输入:{Text,IntWritable}
输出:{Text,IntWritable}
main函数是程序的驱动,其参数指明程序的输入和输出的文件位置,即一个Input文件和一个OutPut文件,这两个参数由运行MapReduce时传入。
3.运行MapReduce程序
首先,确保Hadoop已经开启
检查Java虚拟机状态:
4048 Main
5316 DataNode
5800 NameNode
6142 Jps
5487 SecondaryNameNode
首先看一下IntelliJ生成Jar包的位置,然后我们先键入命令:
bin/hadoop jar ~/IdeaProjects/WordCount-Hadoop/out/artifacts/WordCount_Hadoop_jar/WordCount-Hadoop.jar WordCount test.text output
当当,很荣幸地出错了,所以盲目地键入代码而不搞清楚含义很蛋疼啊。我们一步步分析一下以上的命令吧。
bin/hadoop jar “jar”这个是hadoop的命令行中的执行jar命令,我们要进入bin/hadoop才可以执行hadoop的命令,这里我没有深入去了解,以后好惭愧。
jar后面的是我们的jar包的目录,那么我们想,既然有了命令,有了包,为啥后面还跟着三串似曾相识又说不出所以然来的命令呢?
那么我们再发散一下思维,突然想到前面写程序的时候好像也提到参数?往前一看,发现main作为一个驱动,必须传入输入文件位置和输出文件位置。那么这个推理加上猜测,(再加上一次次一堆堆报错),我可以顺理成章地知道,后面三个参数:第一个是WordCount的入口类,第二个是Input文件,第三个是Output文件。那么问题来了,Iuput的位置怎么搞?在那个目录下?随便什么都行吗?
于是我随便创了个input文件放在用户目录下,得到数次报错如下:
Exception in thread "main" org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: hdfs://localhost:9000/user/coder-z/test.text
at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.singleThreadedListStatus(FileInputFormat.java:321)
吸取实践教训,我发现其实这个input文件需要先上传到hadoop的hdfs文件系统中,而我刚开始配置的时候貌似没有建立相应的hdfs本地文件系统。所以,干脆,直接上传文件到hadoop文件系统中试试,
bin/hadoop fs -copyFromLocal ~/test.text hdfs://localhost:9000/
使用hadoop文件系统中的copyFromLocal命令,将我的input文件传到hdfs中。(这里我也没有去深入了解Hadoop的文件系统,不好意思)
接下来,
键入我们一开始令我懵懵懂懂的命令
bin/hadoop jar ~/IdeaProjects/WordCount-Hadoop/out/artifacts/WordCount_Hadoop_jar/WordCount-Hadoop.jar WordCount hdfs://localhost:9000/test.text output
相应地,对input文件位置做了修改。
值得注意的是,前面的报错说是在hdfs的用户目录下未找到文件,也就是hdfs默认在这个目录中寻找input文件,而我在上传到该用户目录下时却被告知未找到该目录?这个目录是要自己创建的吗?下一次试试看。所以干脆指定input目录在hdfs://localhost:9000下。由于我配置的时候是9000端口,所以是localhost:9000。
好了,键入命令,大功告成(?)。出来的结果令人兴奋,但是,我要的结果呢?
上面提到,我们的最后一个参数是输出位置,那么我们查看一下hadoop文件系统中我们定义为output的
bin/hadoop fs -ls output
首先确认output文件里的内容,应该有两个,一个是_SUCCESS,另一个就是结果output/part-r-00000,后面的00000是reducer输出写入的序号。
查看结果:
bin/hadoop fs -cat output/output/part-r-00000
呼,大功告成(?)
下面一次笔记准备写MapReduce的执行过程(浅略)和初步的MapReudce程序的开发,发现如果要深入理解MapReduce,看教程肯定不够啊,找个时间泛读一下google Mapreduce。
然后是算法算法算法和写代码写代码写代码功底啊,要加油!