(三)hadoop中FileInputFormat类的getSplits获取InputSplit的过程

来源:互联网 发布:蛇蛇大作战网络未连接 编辑:程序博客网 时间:2024/06/06 00:50

FileInputFormat继承了抽象类InputFormat,来看一下InputFormat的源码:

public abstract class InputFormat<K, V> {  public abstract     List<InputSplit> getSplits(JobContext context) throws IOException, InterruptedException;  public abstract     RecordReader<K,V> createRecordReader(InputSplit split,TaskAttemptContext context) throws IOException,                                    InterruptedException;}

InputFormat 主要用于描述输入数据的格式, 它提供以下两个功能。
(1)数据切分 : 按照某个策略将输入数据切分成若干个 InputSplit, 以便确定 Map Task 个数。对应的就是getSplits方法。
(2)为 Mapper 提供输入数据: 给定某个 InputSplit, 能将其解析成一个个 key/value 对。对应的就是createRecordReader方法。

getSplits 方法主要完成数据切分的功能, 它会尝试着将输入数据切分成 InputSplit,并放入集合List中返回。
InputSplit有以下特点:
(1)逻辑分片 : 它只是在逻辑上对输入数据进行分片, 并不会在磁盘上将其切分成分片进行存储。 InputSplit 只记录了分片的元数据信息, 比如起始位置、 长度以及所在的节点列表等。
(2)可序列化: 在 Hadoop 中, 对象序列化主要有两个作用: 进程间通信和永久存储。 此处, InputSplit 支持序列化操作主要是为了进程间通信。 作业被提交到 JobTracker 之前, Client 会调用作业 InputFormat 中的 getSplits 函数, 并将得到的 InputSplit 序列
化到文件中。 这样, 当作业提交到 JobTracker 端对作业初始化时, 可直接读取该文件, 解析出所有 InputSplit, 并创建对应的 Map Task。而createRecordReader则根据InputSplit ,将其解析成一个个 key/value 对。


现在再来看FileInputFormat中对这两个方法的具体实现。

先来看FileInputFormat的定义:

public abstract class FileInputFormat<K, V> extends InputFormat<K, V> {}

FileInputFormat也是一个抽象类,继承了InputFormat这个抽象类。
再来看看FileInputFormat关于getSplits的实现,源码如下:

public List<InputSplit> getSplits(JobContext job) throws IOException {  long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));  long maxSize = getMaxSplitSize(job);    // generate splits  List<InputSplit> splits = new ArrayList<InputSplit>();  List<FileStatus>files = listStatus(job);  for (FileStatus file: files) {    Path path = file.getPath();    FileSystem fs = path.getFileSystem(job.getConfiguration());    long length = file.getLen();    BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);    if ((length != 0) && isSplitable(job, path)) {       long blockSize = file.getBlockSize();      long splitSize = computeSplitSize(blockSize, minSize, maxSize);      long bytesRemaining = length;      while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {        int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);        splits.add(new FileSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts()));        bytesRemaining -= splitSize;      }      if (bytesRemaining != 0) {        splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining, blkLocations[blkLocations.length-1].getHosts()));      }    } else if (length != 0) {      splits.add(new FileSplit(path, 0, length, blkLocations[0].getHosts()));    } else {      //Create empty hosts array for zero length files     splits.add(new FileSplit(path, 0, length, new String[0]));   }}

我们来一块一块的分析,

long minSize = Math.max(getFormatMinSplitSize(),                                                                    getMinSplitSize(job));long maxSize = getMaxSplitSize(job);

不难理解,这里是获取InputSplit的size的最小值和最大值,最小值minSize是通过取getFormatMinSplitSize()和getMinSplitSize(job))中的较大的值
getFormatMinSplitSize方法的源码如下:

protected long getFormatMinSplitSize() {  return 1;}

直接返回1,单位是B.
而getMinSplitSize方法的源码如下:

public static long getMinSplitSize(JobContext job) {   return job.getConfiguration().getLong("mapred.min.split.size", 1L);}

返回的是从配置文件中读取“mapred.min.split.size”属性的value值,“mapred.min.split.size”是需要用户自己添加配置的,配置在mapred-site.xml文件中。
这样一来,minSize的值取决于用户配置的mapred.min.split.size和1B中的较大值
maxSize的大小是由getMaxSplitSize方法确定的,源码如下:

public static long getMaxSplitSize(JobContext context) {   return context.getConfiguration().getLong("mapred.max.split.size",Long.MAX_VALUE);}

若“mapred.max.split.size”属性值读取不到,则返回Long.MAX_VALUE,否则返回“mapred.max.split.size”属性的值。

getSplits方法中有一句代码值得关注:

long splitSize = computeSplitSize(blockSize, minSize, maxSize);

这里是确定了InputSplit的大小,computeSplitSize方法源码如下:

protected long computeSplitSize(long blockSize, long minSize,long maxSize) {  return Math.max(minSize, Math.min(maxSize, blockSize));}

上面说过,minSize的值取决于用户配置的mapred.min.split.size和1B中的较大值。 maxSize的值取决于用户配置的mapred.max.split.size和Long.MAX_VALUE中的较大值。blockSize则是HDFS的默认块大小。

获取到splitSize 后,文件将被切分成大小为splitSize的InputSplit,最后剩下不足splitSize的数据块单独成为一个InputSplit。
那接下来毫无疑问就是按照splitSize 来切分文件了(逻辑上的切分)。


再看getSplits方法的代码块:

long bytesRemaining = length;while (((double) bytesRemaining)/splitSize >SPLIT_SLOP) {   int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);   splits.add(new FileSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts()));   bytesRemaining -= splitSize;}if (bytesRemaining != 0) {   splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining,   blkLocations[blkLocations.length-1].getHosts()));}

这里就是切分的核心代码了,bytesRemaining 表示的是切分后,剩余的待切分的文件大小,初始值就是文件大小【length】,splitSize就是InputSplit的大小,SPLIT_SLOP是一个常量值,定义如下:

private static final double SPLIT_SLOP = 1.1;//10% slop

意思就是当剩余文件大小bytesRemaining与splitSize的比值还大于1.1的时候,就继续切分,否则,剩下的直接作为一个InputSplit。
敲黑板,划重点:并不一定非得bytesRemaining小于splitSize才停止划分哦,只要bytesRemaining/splitSize<=1.1就会停止划分,将剩下的作为一个InputSplit

我们还可以看到,

splits.add(new FileSplit(path, length-bytesRemaining, splitSize,blkLocations[blkIndex].getHosts()));

这里四个参数表示的意思是该InputSplit所在的(路径,起始位置,大小,所在的 host(节点) 列表)

以上就知道getSplits获取InputSplit的过程。