hadoop wordcount source code analysis

来源:互联网 发布:姚明nba数据 编辑:程序博客网 时间:2024/05/16 06:32


http://radarradar.iteye.com/blog/289261

再看一下Reduce的实现:

Java代码  收藏代码
  1. /** 
  2.    * Reduce是一个内部静态类。作为统计单词数量的中间结果类,由于这个例子简单无须执行中间结果的合并。 
  3.    */  
  4. public static class Reduce extends MapReduceBase  
  5.     implements Reducer<Text, IntWritable, Text, IntWritable> {  
  6.       
  7.     public void reduce(Text key, Iterator<IntWritable> values,  
  8.                        OutputCollector<Text, IntWritable> output,   
  9.                        Reporter reporter) throws IOException {  
  10.       int sum = 0;  
  11.       while (values.hasNext()) {  
  12.         sum += values.next().get();  
  13.       }  
  14.       output.collect(key, new IntWritable(sum));  
  15.     }  
  16. }  

 

reduce方法的第二个参数为Iterator<IntWritable> values,是一个迭代器类型,即是多个value的迭代器,通过这个迭代器可以得到多个value。又由于第一个参数指定了key,那么这个迭代器就是与这个key相关的了,即每个value都是key的value,如果统计词频,只要将多个value进行求和运算即可。

最后同样要输出了,这次是输出到文件系统中的指定文件中了,因为是最终结果。

最后一个就是Map/Reduce的核心驱动部分了,目的就是要让WordCount这个工具正常地运行起来,看run方法的实现:

Java代码  收藏代码
  1. /** 
  2.    * map/reduce程序的驱动部分,用于实现提交map/reduce任务。 
  3.    */  
  4. public int run(String[] args) throws Exception {  
  5.     JobConf conf = new JobConf(getConf(), WordCount.class);  
  6.     conf.setJobName("wordcount");  
  7.   
  8.     // the keys are words (strings)  
  9.     conf.setOutputKeyClass(Text.class);  
  10.     // the values are counts (ints)  
  11.     conf.setOutputValueClass(IntWritable.class);  
  12.       
  13.     conf.setMapperClass(MapClass.class);          
  14.     conf.setCombinerClass(Reduce.class);  
  15.     conf.setReducerClass(Reduce.class);  
  16.       
  17.     List<String> other_args = new ArrayList<String>();  
  18.     for(int i=0; i < args.length; ++i) {  
  19.       try {  
  20.         if ("-m".equals(args[i])) {  
  21.           conf.setNumMapTasks(Integer.parseInt(args[++i]));  
  22.         } else if ("-r".equals(args[i])) {  
  23.           conf.setNumReduceTasks(Integer.parseInt(args[++i]));  
  24.         } else {  
  25.           other_args.add(args[i]);  
  26.         }  
  27.       } catch (NumberFormatException except) {  
  28.         System.out.println("ERROR: Integer expected instead of " + args[i]);  
  29.         return printUsage();  
  30.       } catch (ArrayIndexOutOfBoundsException except) {  
  31.         System.out.println("ERROR: Required parameter missing from " +  
  32.                            args[i-1]);  
  33.         return printUsage();  
  34.       }  
  35.     }  
  36.     // Make sure there are exactly 2 parameters left.  
  37.     if (other_args.size() != 2) {  
  38.       System.out.println("ERROR: Wrong number of parameters: " +  
  39.                          other_args.size() + " instead of 2.");  
  40.       return printUsage();  
  41.     }  
  42.     conf.setInputPath(new Path(other_args.get(0)));  
  43.     conf.setOutputPath(new Path(other_args.get(1)));  
  44.           
  45.     JobClient.runJob(conf);  
  46.     return 0;  
  47. }  

 

在run()方法中,值得注意的是JobConf这个类,它是一个任务配置类。它是Configuration的子类,因为在继承了Configuration的关于Hadoop的基本配置以外,还有自己的一些针对任务的相关配置。

JobConf类应该是相当重要的。我们主要围绕在WordCount这个工具中使用到的一些方法进行了解。

首先要实例化一个JobConf类的对象:

Java代码  收藏代码
  1. JobConf conf = new JobConf(getConf(), WordCount.class);  

 

通过这个初始化代码行来看一下JobConf类的构造方法:

Java代码  收藏代码
  1. public JobConf(Configuration conf, Class exampleClass) {  
  2.     this(conf);    
  3.     setJarByClass(exampleClass);  
  4. }  

 

首先, 调用该类的具有一个Configuration类型参数的构造方法,其实就是继承自Configuration类,如下所示:

Java代码  收藏代码
  1. public JobConf(Configuration conf) {  
  2.     super(conf);  
  3. }  

 

然后,调用setJarByClass()方法,根据指定的类名称来设置当前运行任务的任务配置包含的Jar文件,方法如下所示:

Java代码  收藏代码
  1. public void setJarByClass(Class cls) {  
  2.     String jar = findContainingJar(cls);  
  3.     if (jar != null) {  
  4.       setJar(jar);  
  5.     }     
  6. }  

 

这里首先要查找包含的Jar文件(返回的是Jar文件的字符串描述),如果不空再调用 setJar(jar);为任务配置进行设置。

看一下如何进行查找的,在findContainingJar()方法中有实现过程:

Java代码  收藏代码
  1. private static String findContainingJar(Class my_class) {  
  2.     ClassLoader loader = my_class.getClassLoader();   // 获取到指定类Class my_class的类加载器  
  3.     String class_file = my_class.getName().replaceAll("\\.""/") + ".class"// 获取到类文件  
  4.     try {  
  5.       for(Enumeration itr = loader.getResources(class_file);  
  6.           itr.hasMoreElements();) {  
  7.         URL url = (URL) itr.nextElement();  
  8.         if ("jar".equals(url.getProtocol())) { // 迭代出的URL是否支持jar协议  
  9.           String toReturn = url.getPath(); // 获取这个URL的path  
  10.           if (toReturn.startsWith("file:")) {   
  11.             toReturn = toReturn.substring("file:".length()); //   提取path中“file:”字符串后面的文件名字符串  
  12.           }  
  13.           toReturn = URLDecoder.decode(toReturn, "UTF-8"); // 解码  
  14.           return toReturn.replaceAll("!.*$"""); // 格式化:去掉文件名称中的"!.*$"  
  15.         }  
  16.       }  
  17.     } catch (IOException e) {  
  18.       throw new RuntimeException(e);  
  19.     }  
  20.     return null;  
  21. }  

 

查找获得了一个jar字符串,然后setJar(jar) ,方法如下:

Java代码  收藏代码
  1. /** 
  2.    * Set the user jar for the map-reduce job. 
  3.    *  
  4.    * @param jar the user jar for the map-reduce job. 
  5.    */  
  6. public void setJar(String jar) { set("mapred.jar", jar); }  

 

上面set("mapred.jar", jar);方法是继承自Configuration类的方法,如下所示:

Java代码  收藏代码
  1. /**  
  2.    * Set the <code>value</code> of the <code>name</code> property. 
  3.    *  
  4.    * @param name property name. 
  5.    * @param value property value. 
  6.    */  
  7. public void set(String name, String value) {  
  8.     getOverlay().setProperty(name, value);  
  9.     getProps().setProperty(name, value);  
  10. }  

 

上面把set("mapred.jar", jar); 设置到Properties变量中了,而properties和overlay都是Configuration类的成员:

Java代码  收藏代码
  1. private Properties properties;  
  2. private Properties overlay;  

 

到这里,JobConf conf已经进行了基本的任务配置,加载类设置。

接着就要继续更详细的配置了。

设置任务名称:

Java代码  收藏代码
  1. conf.setJobName("wordcount");  

 

setJobName()方法实现:

Java代码  收藏代码
  1. public void setJobName(String name) {  
  2.     set("mapred.job.name", name);  
  3. }  

 

即,任务名称为wordcount。

设置任务的key的Class:

Java代码  收藏代码
  1. // the keys are words (strings)  
  2. conf.setOutputKeyClass(Text.class);  

 

对应于:

Java代码  收藏代码
  1.   public void setOutputKeyClass(Class<? extends WritableComparable> theClass) {  
  2.     setClass("mapred.output.key.class", theClass, WritableComparable.class);  
  3. }  

 

设置任务的value的Class:

Java代码  收藏代码
  1. // the values are counts (ints)  
  2. conf.setOutputValueClass(IntWritable.class);  

 

对应于:

Java代码  收藏代码
  1. public void setOutputValueClass(Class<? extends Writable> theClass) {  
  2.     setClass("mapred.output.value.class", theClass, Writable.class);  
  3. }  

 

设置MapperClass、CombinerClass、ReducerClass的Class:

Java代码  收藏代码
  1. conf.setMapperClass(MapClass.class);          
  2. conf.setCombinerClass(Reduce.class);  
  3. conf.setReducerClass(Reduce.class);  

 

其中,CombinerClass就是使用的ReducerClass,就在ReducerClass中完成一次完成合并简化操作。

接着往下看,List<String> other_args是接收输入的一些特定参数,这里是指设置Map任务和Reduce任务的数量,即-m和-r参数,通过一个循环判断是否指定了这些参数,如果指定了,要分别将其设置到任务的配置中去,以便任务启动之时能够按照我们定制的方式进行执行。

Java代码  收藏代码
  1. if ("-m".equals(args[i])) {  
  2.   conf.setNumMapTasks(Integer.parseInt(args[++i]));  
  3. else if ("-r".equals(args[i])) {  
  4.   conf.setNumReduceTasks(Integer.parseInt(args[++i]));  
  5. else {  
  6.   other_args.add(args[i]);  
  7. }  

 

一个是设置Map任务数量:

Java代码  收藏代码
  1. public void setNumMapTasks(int n) { setInt("mapred.map.tasks", n); }  

 

另一个是设置Reduce任务数量:

Java代码  收藏代码
  1. public void setNumReduceTasks(int n) { setInt("mapred.reduce.tasks", n); }  

 

使用命令行,要指定任务输入目录和输出目录:

Java代码  收藏代码
  1. conf.setInputPath(new Path(other_args.get(0)));  
  2. conf.setOutputPath(new Path(other_args.get(1)));  

 

设置输入目录的方法调用:

Java代码  收藏代码
  1. public void setInputPath(Path dir) {  
  2.     dir = new Path(getWorkingDirectory(), dir);  
  3.     set("mapred.input.dir", dir.toString());  
  4. }  

 

设置任务输出目录的方法调用:

Java代码  收藏代码
  1. public void setOutputPath(Path dir) {  
  2.  dir = new Path(getWorkingDirectory(), dir);  
  3.  set("mapred.output.dir", dir.toString());  

 

它们都调用了一个得到当前工作目录的绝对路径的方法getWorkingDirectory(),如下所示:

Java代码  收藏代码
  1. /** 
  2.    * Get the current working directory for the default file system. 
  3.    *  
  4.    * @return the directory name. 
  5.    */  
  6. public Path getWorkingDirectory() {  
  7.     String name = get("mapred.working.dir");  
  8.     if (name != null) {  
  9.       return new Path(name);  
  10.     } else {  
  11.       try {  
  12.         Path dir = FileSystem.get(this).getWorkingDirectory();  
  13.         set("mapred.working.dir", dir.toString());  
  14.         return dir;  
  15.       } catch (IOException e) {  
  16.         throw new RuntimeException(e);  
  17.       }  
  18.     }  
  19. }  

 

最后,任务配置完成后,进行启动:

Java代码  收藏代码
  1. JobClient.runJob(conf);  

 

这个启动过程可是非常复杂了,你可以通过JobClient类的runJob()方法看到。


原创粉丝点击