Hadoop-MapReduce-FileInputFormat切片getSplits()源码分析,MapReduce InputSplit(切片)与HDFS Block(块)对比


    任务提交流程:WordCount.main() -> Job.waitForCompletion() -> Job.submit() -> Job.connect() -> Cluster.Cluster() -> Cluster.initialize() -> YarnClientProtocolProvider.create() -> JobSubmitter.sbumitJobInternal() -> YARNRunner.submitJob()
    其中,JobSubmitter.submitJobInternal 方法中调用了 int maps = writeSplits(job, submitJobDir);

private int writeSplits(org.apache.hadoop.mapreduce.JobContext job,
    Path jobSubmitDir) throws IOException,
    InterruptedException, ClassNotFoundException {
    
    
  JobConf jConf = (JobConf)job.getConfiguration();
  int maps;   //切片个数
  if (jConf.getUseNewMapper()) {
    
    
    maps = writeNewSplits(job, jobSubmitDir); //跳进这里
  } else {
    
    
    maps = writeOldSplits(jConf, jobSubmitDir);
  }
  return maps;
}

    InputFormat是个抽象类,FileInputFormat继承该类

private <T extends InputSplit>
int writeNewSplits(JobContext job, Path jobSubmitDir) throws IOException,
    InterruptedException, ClassNotFoundException {
    
    
  Configuration conf = job.getConfiguration();
  InputFormat<?, ?> input =
    ReflectionUtils.newInstance(job.getInputFormatClass(), conf);  //输入对象,InputFormat是个抽象类  

  List<InputSplit> splits = input.getSplits(job); //调用InputFormat的实现类FileInputFormat的getSplits方法
  T[] array = (T[]) splits.toArray(new InputSplit[splits.size()]);

  // sort the splits into order based on size, so that the biggest
  // go first
  Arrays.sort(array, new SplitComparator());   //对切片的大小进行排序,最大的放最前面
  JobSplitWriter.createSplitFiles(jobSubmitDir, conf, 
      jobSubmitDir.getFileSystem(conf), array);   //创建Split文件 
  return array.length;
}

getSplits()分析

    InputSplit值记录了切片的元数据信息,比如起始位置,长度及所在的节点列表。

  public List<InputSplit> getSplits(JobContext job) throws IOException {
    
       //文件切片
    StopWatch sw = new StopWatch().start();   //纳秒级计时
    long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));   //最小切割块大小为1
    long maxSize = getMaxSplitSize(job);   //最大切割快大小为9223372036854775807

    // generate splits
    List<InputSplit> splits = new ArrayList<InputSplit>();   //存储切片的list
    List<FileStatus> files = listStatus(job);   //获取多个文件的文件信息
    for (FileStatus file: files) {
    
       //对每个文件单独切片
      Path path = file.getPath();   //获取文件路径
      long length = file.getLen();   //获取文件大小
      if (length != 0) {
    
    
        BlockLocation[] blkLocations;
        if (file instanceof LocatedFileStatus) {
    
       //含有数据块位置信息的文件 
          blkLocations = ((LocatedFileStatus) file).getBlockLocations();
        } else {
    
       //一般文件
          FileSystem fs = path.getFileSystem(job.getConfiguration());
          blkLocations = fs.getFileBlockLocations(file, 0, length);   //块信息
        }
        if (isSplitable(job, path)) {
    
       //文件可切割,不是压缩文件或者是可切割的压缩文件
          long blockSize = file.getBlockSize();   //数据块大小,本地是32M;集群Hadoop2.0是128M,1.0是64M
          long splitSize = computeSplitSize(blockSize, minSize, maxSize);
          //切片大小,一般等于块大小,减少数据传输开销
          //computeSplitSize:Math.max(minSize, Math.min(maxSize, blockSize));
          //通过调整minSize或maxSize调整切片大小

          long bytesRemaining = length;   //记录切片后剩余的文件长度
          while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
    
    
          //剩余的文件长度 ÷ 切片长度 > 1.1才能执行while循环
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                        blkLocations[blkIndex].getHosts(),
                        blkLocations[blkIndex].getCachedHosts()));
            bytesRemaining -= splitSize;
          }

          if (bytesRemaining != 0) {
    
       //剩余的长度不为0,没切完
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
                       blkLocations[blkIndex].getHosts(),
                       blkLocations[blkIndex].getCachedHosts()));
          }
        } else {
    
     // not splitable
          if (LOG.isDebugEnabled()) {
    
    
            // Log only if the file is big enough to be splitted
            if (length > Math.min(file.getBlockSize(), minSize)) {
    
    
              LOG.debug("File is not splittable so no parallelization "
                  + "is possible: " + file.getPath());
            }
          }
          splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
                      blkLocations[0].getCachedHosts()));   //直接整个文件作为一个切片
        }
      } else {
    
       //长度为0的文件
        //Create empty hosts array for zero length files
        splits.add(makeSplit(path, 0, length, new String[0]));
      }
    }
    // Save the number of input files for metrics/loadgen
    job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
    sw.stop();   //计时结束
    if (LOG.isDebugEnabled()) {
    
    
      LOG.debug("Total # of splits generated by getSplits: " + splits.size()
          + ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
    }
    return splits;
  }
  protected boolean isSplitable(JobContext context, Path file) {
    
    
    final CompressionCodec codec =
      new CompressionCodecFactory(context.getConfiguration()).getCodec(file);
    if (null == codec) {
    
    
      return true;
    }
    return codec instanceof SplittableCompressionCodec;
  }
  protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
    
    
    return Math.max(minSize, Math.min(maxSize, blockSize));
  }

    提交完切片到Yarn上,Yarn的MrAPPMaster会根据切片的信息计算开启的MapTask个数。

切片流程总结

  1. 获取此次任务(Job)需要操作的所有文件信息(listStatus)
  2. 遍历处理(切片)每一个文件(每个文件单独处理
    1. 获取当前文件的路径和长度
      1. 如果长度不为0,判断是否可以切片
        1. 如果文件不是压缩文件或者是可切割的压缩文件,则可以切片
          1. 计算切片大小computeSplitSize(blockSize, minSize, maxSize)
            Math.max(minSize, Math.min(maxSize, blockSize))
            blockSize本地是32M;集群Hadoop1.0是64M;2.0是128M
            默认情况下,切片大小等于blockSize。如果想切片大小大于blockSize,则调整minSize;如果想切片大小小于blockSize,则调整maxSize;
          2. 切片前判断文件剩余大小(bytesRemaining)÷ 切片大小(splitSize)是否 > SPLIT_SLOP(1.1),如果满足则切片。循环该步直到不满足条件
          3. 如果切完后剩余的文件大小不等于0,则将剩下的作为一个切片
        2. 如果文件不可以切片,则将整个文件作为一个分片
      2. 如果长度为0,创建一个空的切片
  3. 后续工作,返回切片

    获取切片信息API

  1. FileSplit inputSplit = (FileSplit) context.getInputSplit(); //根据文件类型获取切片信息
  2. String name = inputSplit.getPath().getName(); //获取切片的文件名称

Q:修改切片大小为100M
A:将maxSize修改为100M

Q:修改切片大小为256M
A:将minSize修改为256M

Q:在集群上,一个大小为666M的文件,切片的时候切几块?
A:切6块,大小分别为:128M,128M,128M,128M,128M,26M
    

InputSplit vs Block

  1. 大小:
        block:blockSize本地是32M;集群Hadoop1.0是64M;2.0是128M。块的大小可以通过配置文件修改。文件的所有块(除了最后一块)大小相等,最后一块小于等于blocksize。
        InputSplit:默认情况下,切片大小等于blockSize。用户可以根据数据大小修改切片大小。
  2. 数据形式
        block:块是真实的数据,存放在HDFS上。
        InputSplit:切片是逻辑上的数据,用于在Map和Reduce处理。切片只包含数据的元数据。

Q:在集群上,一个大小为129M的文件,存储的时候存几块,切片的时候切几块?
A:存储时,以128M为单位,所以存储两块,一块为128M,一块为1M
      切片时,129 ÷ 128 < 1.1,所以存一块

    

猜你喜欢

转载自blog.csdn.net/H_X_P_/article/details/105983811