hadoop2.x—mapreduce实战和总结

来源:互联网 发布:java使用postmethod 编辑:程序博客网 时间:2024/05/22 06:33
在eclipse上编写程序,运行在hadoop上。网上很多的例子都是1.x的mr代码,而1.x的代码和2.x的代码是有些区别的。在hadoop官网上可以下载到hadoop的源码包,源码包里面有很多的源代码,hadoop安装包里面的都是些jar文件,jar可以执行,但是无法看里面的源码。接下来详细分析两个案例的代码,并附带个人的hadoop学习总结与大家分享,希望大家给点意见。一个是hadoop源码,一个是hadoop实战的例子。希望大家给点意见.....

第一:hadoop-2.7.3的wordcount的源码文件

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.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.output.FileOutputFormat;import org.apache.hadoop.util.GenericOptionsParser;public class WordCount {  /*WordCount里面定义了两个内部类。一个是TokenizerMapper,继承Mapper类*/  public static class TokenizerMapper       /*Mapper类给出了4个参数,前两个代表的是map要处理的key和value的类型,默认可以指定Text,Object的范围更加宽泛,也是可以的,这里的key和value,默认采用的是以行为单位,可以理解为行号是key,实际是偏移量,每行的内容是value,词频统计非常有效,后面会说一些特殊情况。*/       /*后两个是要输出的key和value的类型。在实际情况中,我们输出的key是以空格分割的单词,value是1(这个1后面要累加,所以用int,intwritable是hadoop的变量,hadoop要在网络中进行计算传递,所用的数据都是可序列化的)。而且这里面的单词肯定有重复的,不重复统计干啥。map函数的从输入到输出就是对数据进行封装处理。*/       extends Mapper<Object, Text, Text, IntWritable>{    /*定义两个变量,一个是value的,一个是key的。输出的时候就用这两个。*/    private final static IntWritable one = new IntWritable(1);    private Text word = new Text();    /*到了实现mapper类里面方法的时候了。map是父类的方法。加@Override更保险,如果这个地方你不小心弄错了,变成自己新的方法,map过程就不会执行了。参数列表中前两个对于map的输入,跟map类的前两个一样。Context是用来封装好输出,交给reduce处理的。*/    public void map(Object key, Text value, Context context                    ) throws IOException, InterruptedException {    /*这里使用到了字符串的分词器,以空格分割,是按照我们输入的每行数据的格式。*/      StringTokenizer itr = new StringTokenizer(value.toString());      /*分词后的数据很像一个迭代器,每次取出一个词,封装成Text类型的key,IntWritable类型的value对输出,这里value是1*/      while (itr.hasMoreTokens()) {        word.set(itr.nextToken());        context.write(word, one);      }    }  } /*reduce的作用就是累加。首先,reduce会收集到具有相同key的key,value对,单词相同的就可以累加,有多少相同的就有多少个1,累加起来就是词数。继承reducer类,四个参数,前两个是map的输出作为reduce的输入,后两个是reduce的输出,输出key是单词,value词数。*/  public static class IntSumReducer        extends Reducer<Text,IntWritable,Text,IntWritable> {    private IntWritable result = new IntWritable();    /*参数列表类型照搬上面的前两个,有点区别,这里输入的是具有相同key的键值对的集合,因此是可迭代类型,每个元素的类型是intwritable的。Context作用一样*/    public void reduce(Text key, Iterable<IntWritable> values,                        Context context                       ) throws IOException, InterruptedException {      /*int sum是用来在这个函数里做累加的,都是相同的key的集合,把value一个一个加起来。最后输出的肯定是序列化的intwritable类型的result,result调用set方法赋值*/      int sum = 0;      for (IntWritable val : values) {        sum += val.get();      }      result.set(sum);      context.write(key, result);    }  }   public static void main(String[] args) throws Exception {   /*conf可以理解为在hadoop的conf文件一样的作用。在运行的时候,需要将conf的配置文件拖到src的目录下,否则会报错的,eclipse读取不到hadoop的配置文件下的东西,自身目录下的文件可以读取*/    Configuration conf = new Configuration();   /*输入的参数,input和output目录里的统计用的文件,在eclipse设置。*/    String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();    if (otherArgs.length < 2) {      System.err.println("Usage: wordcount <in> [<in>...] <out>");      System.exit(2);    }   /*要运行这个mapreduce的作业就要用到job了。提交这个job就要告诉hadoop我们的jar包,我们用的map和reduce的类是什么,hadoop才会按照我们的指示去执行工作。combiner是一种优化的手段,但并不是都有效,比如求平均值。我们最后得到的结果也是在job的setoutputkeyclass和setoutputvalueclass里面设置。这个与reduce的输出是对应的,写入到part-r-xxxx的文件里。*/    Job job = Job.getInstance(conf, "word count");    job.setJarByClass(WordCount.class);    job.setMapperClass(TokenizerMapper.class);    job.setCombinerClass(IntSumReducer.class);    job.setReducerClass(IntSumReducer.class);    job.setOutputKeyClass(Text.class);    job.setOutputValueClass(IntWritable.class);    for (int i = 0; i < otherArgs.length - 1; ++i) {      FileInputFormat.addInputPath(job, new Path(otherArgs[i]));    }    FileOutputFormat.setOutputPath(job,      new Path(otherArgs[otherArgs.length - 1]));   /*waitforcomletion的作用就是提交这个作业执行,成功返回1,并退出*/    System.exit(job.waitForCompletion(true) ? 0 : 1);  }}


现在还有一些问题需要处理。
1、
在map之前,hadoop就进行了一次key,value的划分,按行划分的。但是,如果一个文件里面,给的格式是一个key后面跟着一个value的话,怎么处理?这里,默认是text类型,这个类型是以行\n分割,它的键值是<int(行偏移),text(行的文本)>,在处理wordcount,这种划分的比较适合。而例如文本中给定了key,value的数据,使用keyvalueinputformat的类型进行文本分割比较好。这里默认的分隔符是\t。使用下面的命令显示指定预处理的方法。
job.setInputFormatClass(KeyValueTextInputFormat.class);
2、接着上面的,如果分隔符不是/t,而是逗号,这些预处理怎么处理。hadoop提供了解决方案,设置分隔符就可以了。命令要在job实例化之前。
conf.set("mapreduce.input.keyvaluelinerecordreader.key.value.separator", ",");
3、新版本使用的是iteratable来收集键值对集而不是 iterator,旧版本使用的是output.collect()的方法来实现键值对的写入传递,新版本使用的context来负责写入。这里就不列出1.x的代码了,网上很多很多的例子。

第二:hadoop实战专利引用代码解析


第一个例子说完,就可以自己比较容易的去修改hadoop实战中的例子了。hadoop实战中专利数据的处理用到的是hadoop1.x的。这是部分数据的截取,第一列是专利列,第二列是第一列专利所引用到的其它专利。接下来我们要统计,第二列的每个专利,都有那些在第一列中的专利引用了,value采用逗号分隔并追加的方法。key和value以空格隔开。
资源地址:http://www.nber.org/patents/ 下的cite75_99.txt

输入文件
cit"CITING","CITED"3858241,9562033858241,13242343858241,33984063858241,35573843858241,36348893858242,15157013858242,33192613858242,3668705




输出文件

"CITED" "CITING"1       3964859,464722910000   4539112100000  50313881000006 47142841000007 47666931000011 50333391000017 39086291000026 40430551000033 4190903,49759831000043 40915231000044 4082383,40553711000045 42905711000046 5525001,5918892



我们把它的代码改成2.x的,1.x的代码不说了,直接上2.x的。




import java.io.IOException;import java.util.Iterator;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.Text;import org.apache.hadoop.mapred.JobClient;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.Mapper;import org.apache.hadoop.mapreduce.Reducer;import org.apache.hadoop.mapreduce.Reducer.Context;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;import org.apache.hadoop.util.GenericOptionsParser;import test.WordCount.IntSumReducer;import test.WordCount.TokenizerMapper;public class patent {public static class Map     extends Mapper<Text, Text, Text, Text>{ /*直接使用文本来操作,很方便。这里的输入和输出我们都使用上面说的KeyValueTextInputFormat文本预处理。逗号分隔conf.set("mapreduce.input.keyvaluelinerecordreader.key.value.separator", ","),处理的东西都变成文本,又不统计个数。*/ public void map(Text key, Text value, Context context                 ) throws IOException, InterruptedException {     /*这里的map唯一需要做的就是把key和value颠倒过来,我们的目的就是统计被引用的专利有哪些第一列的专利引用,这里的key,value反过来了*/     context.write(value,key); }}public static class Reduce     extends Reducer<Text,Text,Text,Text> { /*reducer的方法,第一个是key,唯一键,第二个是value,有很多,之前也说了,reduce收集的是具有相同key的键值对的集合,集合进行了封装,类似于<key1 value1,value2,value3>*/ public void reduce(Text key, Iterable<Text> values,                     Context context                    ) throws IOException, InterruptedException {  /*接下来,就是把value的字符串连接成以逗号分隔,value1,value2,value3这种类型的形式,这里的转化成了iterator,因为想要next方法来获取数据,iterator比较好操作,这里的操作有点倒退了,毕竟1.x使用的就是iterator,但在传输上,iterator更具有优势。while循环就不说了,很简单。*/  String csv="";  Iterator<Text> iterator=values.iterator();  while (iterator.hasNext()){  if(csv.length()>0)csv+=",";  csv+=iterator.next().toString();  }  context.write(key, new Text(csv)); }}public static void main(String[] args) throws Exception { Configuration conf = new Configuration();/*设置每一行的key,value划分都是以逗号分隔*/ conf.set("mapreduce.input.keyvaluelinerecordreader.key.value.separator", ","); Job job = Job.getInstance(conf, "patent"); job.setJarByClass(patent.class); job.setMapperClass(Map.class);/*输入的文本使用的是逗号分隔key,value的方法,输出是text,这个设不设都可以*/ job.setInputFormatClass(KeyValueTextInputFormat.class); job.setOutputFormatClass(TextOutputFormat.class); job.setReducerClass(Reduce.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class);/*沿用了1.x的方法,个人觉得使用这个看起来比2.x那个简洁多*/ Path in = new Path(args[0]); Path out = new Path(args[1]); FileInputFormat.setInputPaths(job, in); FileOutputFormat.setOutputPath(job, out);/*执行,输出结果上面已经给了*/ System.exit(job.waitForCompletion(true)?0:1);}}

额外的说明
1、
这里说一下输入的参数,这个是在eclipse里面设置的。如果你愿意可以吧args[]直接替换成路径。
/user/root/test/cite75_99.txt /user/root/output
输入和输出之间有个空格。因为hadoop2.x使用的是/user/username/file的格式,这里就在hdfs里面创建了/user/root/的目录。
2、扯一下文件分片的问题。文件大小是251M,默认的datanode一个片的大小是128M,1.x是64M吧,因此250M的文件会被分成2片。可以通过http://localhost:50070/的utilities菜单栏的browse the file system,输入/user/root/test-----这是我存放文件的目录,可以看到文件的详细信息。存放也是按块均匀的(我的集群是2个datanode,因此一个128,一个122,它显示大小是以字节为单位的*1024*1024)存放在各个datanode上,mapred.min.split.size可以设定大小。
3、eclipse运行的时候需要复制配置文件到src目录下,例如log4j.properties就是用来显示日志的.关于日志文件。有两种,一个是log一个是out。log是通过log4j记录,记录大部分的日志信息。out是标准输出和标准错误日志,少量记录。
日志的命名规则=框架名称-用户名-进程名-主机名-日志格式后缀
可以开一个终端使用命令:tail -f 日志名-----------会停顿在结尾,当有新日志刷新的时候,会滚动显示
MapReduce程序日志分为历史作业日志和container日志
历史作业日志
包含多少个Map,用了多少个Reduce,作业提交时间,作业启动时间,作业完成时间等
container日志---yarn
ApplicationMaster日志和普通的task日志 ${HADOOP_HOME}/logs/userlogs
Application Master的路径是/tmp/hadoop-yarn/staging下,这里的/tmp/是hdfs文件系统的目录,通过web可以查看
/tmp/hadoop-yarn/staging/history/done/年/月/日/  按日期存放的
4、静态内部map和reduce类
好处是简化代码管理,这些内部类与主类独立,不交互,在执行时,采用不同的jvm的各类节点复制并运行map reduce,其它作业类在客户机上执行。
5、 Job job_local1970859078_0001 running in uber mode : false
打印的日志会有这么一句话,uber mode如果设置为true则使用一台jvm执行,伪分布测试小数据的话会非常的快,毕竟,开的JVM太多会有额外的开销,实际运行是在不同节点开启多个jvm同时运行的。通过把mapreduce.job.ubertask.enable设置为true可以开启。
6、 我们常说namenode存储的是元数据,hadoop的fsimage元数据存放在namenode定义的目录中,用来联系datanode
fsimage保存了最新的元数据检查点。
edits保存自最新检查点后的命名空间的变化,secondNamenode的协助作用体现在,会周期性的合并为fsimage
默认这两文件都放在namenode文件夹下面,可以分开设置并存放。
dfs.namenode.name.dir和dfs.namenode.edits.dir是设置存放目录的
dfs.namenode.checkpoint.dir是设置secondarynamenode进行合并的目录
7、 aggregation日志聚合
日志聚集是YARN提供的日志中央化管理功能,它能将运行完成的Container/任务日志上传到HDFS上,从而减轻NodeManager负载,且提供一个中央化存储和分析机制。默认情况下,Container/任务日志存在在各个NodeManager上。日志聚合的目录是/tmp/logs
yarn-site.xml可以进行配置。
开启的话是yarn.log-aggregation-enable-------true
聚合的默认日期 yarn.log-aggregation.retain-seconds----------------默认是以S为单位
8、 eclipse运行hadoop

我的出现了几个问题。在把core、hdfs和log配置文件拷到src目录下的时候,是可以运行在hadoop上的。运行在yarn上需要四个配置文件都拷贝,但是还是出现了找不到内部类的错误。后来直接将工程导出为jar包,使用/bin/yarn命令运行是没有问题的。好像是eclipse缺失yarn的jar,暂时未解决。

1 0