Hadoop的分块、分片分析

org.apache.hadoop.mapred.FileInputFormat中268行,getSplits方法实现:

public InputSplit[] getSplits(JobConf job, int numSplits)
    throws IOException {
    Stopwatch sw = new Stopwatch().start();
    FileStatus[] files = listStatus(job);
    
    // Save the number of input files for metrics/loadgen
    job.setLong(NUM_INPUT_FILES, files.length);
    long totalSize = 0;                           // compute total size
    for (FileStatus file: files) {                // check we have valid files
      if (file.isDirectory()) {
        throw new IOException("Not a file: "+ file.getPath());
      }
      totalSize += file.getLen();
    }

    long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
    long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
      FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);

    // generate splits
    ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
    NetworkTopology clusterMap = new NetworkTopology();
    for (FileStatus file: files) {
      Path path = file.getPath();
      long length = file.getLen();
      if (length != 0) {
        FileSystem fs = path.getFileSystem(job);
        BlockLocation[] blkLocations;
        if (file instanceof LocatedFileStatus) {
          blkLocations = ((LocatedFileStatus) file).getBlockLocations();
        } else {
          blkLocations = fs.getFileBlockLocations(file, 0, length);
        }
        if (isSplitable(fs, path)) {
          long blockSize = file.getBlockSize();
          long splitSize = computeSplitSize(goalSize, minSize, blockSize);

          long bytesRemaining = length;
          while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
            String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,
                length-bytesRemaining, splitSize, clusterMap);
            splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                splitHosts[0], splitHosts[1]));
            bytesRemaining -= splitSize;
          }

          if (bytesRemaining != 0) {
            String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations, length
                - bytesRemaining, bytesRemaining, clusterMap);
            splits.add(makeSplit(path, length - bytesRemaining, bytesRemaining,
                splitHosts[0], splitHosts[1]));
          }
        } else {
          String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,0,length,clusterMap);
          splits.add(makeSplit(path, 0, length, splitHosts[0], splitHosts[1]));
        }
      } else { 
        //Create empty hosts array for zero length files
        splits.add(makeSplit(path, 0, length, new String[0]));
      }
    }
    sw.stop();
    if (LOG.isDebugEnabled()) {
      LOG.debug("Total # of splits generated by getSplits: " + splits.size()
          + ", TimeTaken: " + sw.elapsedMillis());
    }
    return splits.toArray(new FileSplit[splits.size()]);
  }

主要信息

totalSize:是job所有输入的总大小。

numSplits:自己设定的split个数。

goalSize:是输入总大小与提示Map task数量的比值,即期望每个Mapper处理多少的数据,仅仅是期望,具体处理的数据数由下面的computeSplitSize决定。

minSplitSize:“private long minSplitSize = 1”。

minSize:取的1和『job.getLong(“mapred.min.split.size”, 1)是获取配置文件中设置的值,若没有设置,则取1』中较大的一个。

blockSize:hadoop 2.x 默认为128M。

splitSize:就是最终每个Split的大小。

分析

先看computeSplitSize方法的实现:首先在goalSize和blockSize中取较小的,然后与minSize相比取较大的。

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

一个splits即一个map,只要搞清楚片的大小,就能计算出运行时的map数。而一个split的大小是由goalSize, minSize, blockSize这三个值决定的。computeSplitSize的逻辑是,先从goalSize和blockSize两个值中选出最小的那个(比如一般不设置map数,这时blockSize为当前文件的块size;而goalSize是文件大小除以用户设置的map数得到的,如果没设置的话,默认是1),在默认的大多数情况下,blockSize比较小。然后再取blockSize和minSize中最大的那个。而minSize如果不设置的话一般为1,这样得出的一个splits的size就是blockSize,即一个块一个map,有多少块就有多少map。

input_file_num : 输入文件的个数
(1)默认map个数
如果不进行任何设置,默认的map个数是和blcok_size相关的。
default_num = total_size / block_size;
(2)期望map数量
可以通过参数mapred.map.tasks来设置程序员期望的map个数,但是这个个数只有在大于default_num的时候,才会生效。
goal_num =mapred.map.tasks;
(3)设置处理的文件大小
可以通过mapred.min.split.size 设置每个task处理的文件大小,但是这个大小只有在大于
block_size的时候才会生效。
split_size = max(mapred.min.split.size,block_size);
split_num = total_size / split_size;
(4)计算的map个数
compute_map_num = min(split_num, max(default_num, goal_num))
除了这些配置以外,mapreduce还要遵循一些原则。 mapreduce的每一个map处理的数据是不能跨越文件的,也就是说max_map_num <= input_file_num。 所以,最终的map个数应该为:
final_map_num = min(compute_map_num, input_file_num)
经过以上的分析,在设置map个数的时候,可以简单的总结为以下几点:
i)如果想增加map个数,则设置mapred.map.tasks 为一个较大的值。
ii)如果想减小map个数,则设置mapred.min.split.size 为一个较大的值。

数据以单文件且文件内容以一行的形式表现
单位,只是一个逻辑概念,每个InputSplit并没有对文件进行实际的切割,只是记录了要处理的数据的位置(包括文件的path和hosts)和长度(由start和length决定)。因此以行记录形式的文本,可能存在一行记录被划分到不同的block,甚至不同的DataNode上去。通过分析getSplits方法,可以得出,某一行记录同样也可能被划分到不同的InputSplit。但是不会对map之类的操作造成影响,原因是与FileInputFormat关联的RecordReader中的readLine方法在读取数据的时候不会受到InputSplit划分的限制,当读取一行的时候会一直读取直到读取到行结束符为止,因此其能够读取不同的InputSplit,直到把这一行数据读取完成。且当下一次在另外一个InputSplit中再次读取到该行的数据时不会再次进行计算。

举例

默认情况下,HadoopRDD一个分区最大是blockSize=128M,假设一个文件大小是totalSize=1G+10M,但是我设置4个分区(goalSize=258M),根据Math.max(minSize, Math.min(goalSize, blockSize),那么在计算分区个数的时候会计算为9个分区,最后一个分区大小为10M。

相关参考:
《Spark以textFile方式读取文件源码》
https://www.cnblogs.com/dhName/p/11068582.html
《Spark没有读取HDFS文件的方法?那textFile是怎么读的?》
https://blog.csdn.net/qq_41946557/article/details/103113304

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值