这一章都是文字叙述,不需要写源代码了。一般情况下,只需要记住这些东西就可以了。
Hadoop处理大数据。
大数据以文件的形式存储在HDFS。
大文件被划分成文件块存贮,每个文件块有固定的大小,通常是64M,或者128M,或者255M。
我们在第2章写了一个WordCount的MapReduce程序,最关键部分是Mapper和Reducer。在做MapReuce时,先做Map,再做Reduce。Hadoop的框架让我们不需要关注Mapper和Reducer之外的地方,但我们肯定会想,数据是怎么从HDFS传给Mapper的?Reducer处理完数据之后,又是怎么将数据存储到HDFS的?
将数据从HDFS传到Mapper是由InputFormat类实现的。
将数据从Reducer存储到HDFS是由OutputFormat类实现的。
先解释一下Java的抽象类。抽象类就是声明了抽象函数的类。假如A是一个抽象类,它有一个抽象函数do_it,那么它只是声明有do_it这个函数,但并没有实现它的功能。抽象类只能作为类的模板,也就是说抽象类不能实例化。假设,类B继承了类A,B也就有了dot_it函数,如果B是一个具体的类,它就必须在自己的内部实现do_it函数的功能。
1.InputFormat类
InputFormat类是一个抽象类。InputFormat类定义了两个抽象函数。这两个抽象函数是:
“abstract List<InputSplit> getSplits(JobContext context) ”
“abstract RecordReader<K,V> createRecordReader(InputSplit split,TaskAttemptContext context) ”
如果检查一下源代码,InputFormat抽象类的定义是在InputFormat.java,代码非常短,注释之外只有这两行抽象函数声明。
函数getSplits的功能,是将输入的HDFS文件切分成若干个spit。在Hadoop集群里的每个节点做MapReduce时候处理的时候,每次只处理一个split,所以split是MapReduce处理的最小单元。
函数createRecordReader的功能,是创建RecordReader对象,这个RecordReader对象根据split的内容,将split解析成若干个键值对。在做MapReduce的时候,Mappper会不断地调用RecordReader的功能,从RecordReader里读取键值对,然后用map函数进行处理。
InputFormat类是一个抽象类,它并不具体负责这两个功能的实现。它有3个继承类,DBInputFormat类,DelegatingInputFormat类和FileInputFormat类。其中,DBInputFormat类是处理从数据库输入,DelegatingInputFormat类是用在多个输入处理,FileInputFormat类是处理基于文件的输入。
以fileInputFormat类为例。FileInputFormat类是一个抽象类,它在InputFormat类的基础上,增加一些跟文件操作相关的函数。它实现了getSplits函数,但没实现createRecordReader函数,它把createRecordReader的实现留给继承类去做。在getSplits函数里,最重要的是这段:
-
// generatesplits
//这是返回值
List<InputSplit>splits = new ArrayList<InputSplit>();
//获取HDFS文件的信息,FileStatus在前面的章节使用过。
List<FileStatus>files= listStatus(job);
//对作业的每个文件都进行处理
for(FileStatus file: files) {
//获取文件路径
Path path =file.getPath();
FileSystemfs = path.getFileSystem(job.getConfiguration());
//获取文件长度
long length= file.getLen();
//获取文件在HDFS上存储的文件块的位置信息
BlockLocation[]blkLocations = fs.getFileBlockLocations(file, 0, length);
if ((length!= 0) && isSplitable(job, path)) {
//获取文件块的大小
longblockSize = file.getBlockSize();
//根据文件块大小,最小尺寸,最大尺寸,计算出split的大小
longsplitSize = computeSplitSize(blockSize, minSize, maxSize);
//这段代码是根据splitSize,每次计算一个split的块位置和所在主机的位置。
//然后生成split对象存储。
longbytesRemaining = length;
while(((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
intblkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(newFileSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts()));
bytesRemaining-= splitSize;
}
//最后剩下的不够一个splitSize的数据单独做一个split。
if(bytesRemaining != 0) {
splits.add(newFileSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkLocations.length-1].getHosts()));
}
} else if(length != 0) {
//如果文件很小,就直接做成一个split
splits.add(newFileSplit(path, 0, length, blkLocations[0].getHosts()));
} else {
//如果文件尺寸是0,空文件,就创建一个空主机,主要是为了形式上一致。
splits.add(newFileSplit(path, 0, length, new String[0]));
}
}
FileInputFormat有5个继承类,包括CombineFileInputFormat类,KeyValueTextInputFormat类,NLineInputFormat类,SequenceFileInputFormat类和TextInputFormat类。这几个有抽象类,也有具体类。
以TextInputFormat类为例,它实现了createRecordReader函数,非常简单,函数体只有一个语句,返回一个LineRecordReader。
LineRecordReader类继承了抽象类RecordReader。抽象类RecordReader定义的全是抽象函数。LineRecordReader每次从一个InputSplit里读取一行文本,以这行文本在文件中的偏移量为键,以这行文本为值,组成一个键值对,返回给Mapper处理。
2.OutputFormat类
OutputFormat类将键值对写入存储结构。一般来说,Mapper类和Reducer类都会用到OutputFormat类。Mapper类用它存储中间结果,Reducer类用它存储最终结果。
OutputFormat是个抽象类,这个类声明了3个抽象函数:
publicabstract RecordWriter<K, V> getRecordWriter(TaskAttemptContext context)
publicabstract void checkOutputSpecs(JobContext context)
publicabstract OutputCommitter getOutputCommitter(TaskAttemptContextcontext)
其中,最主要的函数是getRecordWriter返回RecordWriter,它负责将键值对写入存储部件。函数checkOutputSpecs检查输出参数是否合理,一般是检查输出目录是否存在,如果已经存在就报错。函数getOutputCommitter获取OutputCommitter,OutputCommitter类是负责做杂活的,诸如初始化临时文件,作业完成后清理临时目录临时文件,处理作业的临时目录临时文件等等。
OutputFormat类4个继承类,有DBOutputFormat,FileOutputFormat,FilterOutputFormat,NullOutputFormat。顾名思义,DBOutputFormat是将键值对写入到数据库,FileOutputFormat将键值对写到文件系统,FilterOutputFormat将其实是提供一种将OutputFormat进行再次封装,类似Java的流的Filter方式,NullOutputFormat将键值对写入/dev/null,相当于舍弃这些值。
以FileOutputFormat为例。FileOutputFormat是一个抽象类。它有两个继承类,SequenceFileOutputFormat和TextOutputFormat。SequenceFileOutputFormat将键值对写入HDFS的顺序文件。TextOutputFormat将数据写入HDFS的文本文件。写入过程类似第3章HDFS文件操作。