代码清单3-2中,WordCountOldAPI.java文件展示的是使用已经被废弃的旧API编写的单词计数WordCount程序。但是旧API已经被广泛使用,很多以前写的程序仍在运行。旧API并不会完全消失,所以,知道如何使用它们还是很重要的,读者在维护老代码的时候会经常碰到它们。
我 们从分析main()函数开始。下面列出了必须要理解的重要概念:
JobConf是Hadoop框架中配置MapReduce任务的主要接口。框架会按照JobConf对象中的配置信息来执行作业。
TextInputFormat向Hadoop框架声明其输入文件为文本格式。它是InputFormat的子类。(各种InputFormat类会在第7章中进行更细致地讲解。)现在,我们知道了TextInputFormat类会读取输入文件中的每行作为一个记录。
TextOutputFormat指定了该MapReduce作业的输出情况。举个例子,它会检测其输出目录是否已经存在。如果输出目录已经存在,Hadoop系统会拒绝该作业的执行。Hadoop作业会在海量数据上运行,它通常会花费几分钟或者几小时才行运行完毕。执行的作业偶尔会出现故障,导致其所有工作丢失,这种情况下,该作业会重新运行(rerun)。各种特定的OutputFormat类会在第7章中讲解,但是,现在我们可以知道,TextOutputFormat类用来声明其MapReduce作业的输出是文本格式文件。
FileInputFormat.setInputPaths(conf, new Path(args[0]))向Hadoop框架声明了要读取的文件所在的目录。该输入目录中可以包含一个或多个文件,并且每个文件中每行含有一个单词。注意在setInputPaths中使用的是复数。这个方法可以给Hadoop程序配置一个文件目录数组作为其输入数据路径。这些输入目录中的所有文件构成了该作业的输入数据源。
FileOutputFormat.setOutputPath(conf, new Path(args[1]))指定了作业的输出目录。作业最终的结果输出会保存到该目录下。
conf.setOutputKeyClass和conf.setOutputValueClass指定了输出的键(Key)类和值(Value)类,它们应该与Reducer类相匹配。这里似乎有些多余,确实是这样。键(Key)类和值(Value)类必须要同指定的Reducer类一致,否则在作业执行的时候将会抛出RuntimeException异常。
Reducer实例的执行数量默认为1。该值是可以改变的,并且常常可以用来提高程序的运行效率。调用JobConf.class 实例中的setNumReduceTasks(int n)方法就可以配置该参数。
现在你理解了整个程序是如何在Hadoop框架中运行的。当程序开始的时候,Mapper函数会从输入文件夹中的输入文件中读取其数据块。一个文件的字节流会被转换成一个记录(键/值对格式),然后作为Mapper的输入。键是当前行字节偏移量(LongWritable.class实例),值(Text.class实例)是从文件中读取的一行文本数据。在这个简化的例子中,使用的是本书源代码中的示例文件,每行一个单词。
Mapper发送每行的单词和整数1。要注意的是,Hadoop作业程序需要使用与Integer.class对应的系统自带的IntWritable.class类。其原因是Hadoop系统底层I/O传输设计(这些会在接下来的章节介绍)。现在我们只需知道,Hadoop作业程序的Mappers和Reducers的输入和输出必须是Writable.class类型的实例。
紧接着是Hadoop系统的Shuffle阶段,这个过程在上面的列表中并不明显。所有的键(在我们的例子中是单词)在Shuffle/Sort阶段被排序,然后发送给Reducer。逻辑上,Reducer接受到的是一个IntWritable.class类实例的迭代器。实际上,在Reducer中使用的是相同的IntWritable.class类实例。当Reducer迭代地取出键对应的一系列值(在我们的例子中是整数1的一个列表),相同的IntWritable.class类实例被复用。从其内部运行来看,Hadoop系统框架在其迭代器中使用values.next()之后,调用了IntWritable.set(译者注,此处应为IntWritable.get,疑为笔误)方法。当你在获取了这些值,并且作为引用来复用它们的时候要格外注意。因为Hadoop的这个特性(节约内存使用的一种优化),复用values.next()实例的引用会导致意想不到的结果。
最后,当Reducer输出结果的时候,结果被写入到由客户端WordCountOldAPI程序的FileOutputFormat.setOutputPath(conf, new Path(args[1]))指定的输出文件目录中。
Reducer会向输出文件中写入结果键和值的实例。输出文件中的键和值实例的默认分隔符是TAB,这个分隔符可以通过配置参数:mapreduce.textoutputformat.separator来修改。举个例子,在main(...)方法中调用conf.set("mapreduce.textoutputformat.separator",",")会把输出中键/值实例之间的分隔符变换为一个“,”。
在一个真实集群上运行作业的话,配置了多少个Reducers实例运行,就会生成多少个结果文件。本例中,结果文件包含的结果数据是一个单词,后跟该单词的计数。
在集群上提交执行上面这个作业程序的命令行与下面一节中使用的命令行是相似的。我们会在下一节中开发新API的程序时讲解。