hadoop2.x—mapreduce实战和总结

在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,956203
3858241,1324234
3858241,3398406
3858241,3557384
3858241,3634889
3858242,1515701
3858242,3319261
3858242,3668705




输出文件

"CITED" "CITING"
1       3964859,4647229
10000   4539112
100000  5031388
1000006 4714284
1000007 4766693
1000011 5033339
1000017 3908629
1000026 4043055
1000033 4190903,4975983
1000043 4091523
1000044 4082383,4055371
1000045 4290571
1000046 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,暂时未解决。

你可能感兴趣的:(hadoop2.x—mapreduce实战和总结)