MapReduce之数据分片思想

尽管MR由于计算效率问题,已经不适用于大多数业务场景,Hive3开始计算引擎改为TEZ,但MR的经典思路在Hadoop生态各组件都有体现,重温后对各组件原理的理解还有使用都有帮助,如Spark的RDD分区里面的源码就能看到MR分片思想的影子。这里仅拿输入Map前分片(Split)这个动作的源码做窥探,深入挖掘下分片(split)的思想。

一、简单总结

进行MR前,对要计算的各文件按分片逻辑切分,多少个分片则对应产生多少个Map计算。
分片逻辑以某个值(splitSize)为基准,超过其1.1倍时,则再做分片处理,直到文件小于基准值的1.1倍为止。
在没有另外设置的情况下,splitSize的大小对应blockSize的默认大小,即128M。
如文件使用Gzip或Snappy等不支持切分的算法压缩,则不管文件多大都一个分片。

ps:上面建立在没有手动调整参数设置的情况下

二、数据分片(源码解读)

在这里插入图片描述

上图为MR的大致流程,分片(spilt)这动作其实就是MR这个过程的输入,指的是对需要计算的目录文件进行分片。

MR核心思想:分而治之,这思想也是分布式计算的思想,把大文件分成小文件,然后一个作业处理一个小文件,最后再做合并,实现对大数据文件的批处理。

//摘至Hadoop2.7.1源码,文件:FileInputFormat.java

  private static final double SPLIT_SLOP = 1.1;   // 10% slop //切片溢出系数
  private long minSplitSize = 1;

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();
    }
   //无定义numSplits大小则goalSize默认为文件大小
    long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
  //最小切片不设置默认为1
    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(); //文件路径及长度,存储在namenode
      long length = file.getLen();
      if (length != 0) {
        FileSystem fs = path.getFileSystem(job);
        BlockLocation[] blkLocations; //获取文件block块
        if (file instanceof LocatedFileStatus) {
          blkLocations = ((LocatedFileStatus) file).getBlockLocations();
        } else {
          blkLocations = fs.getFileBlockLocations(file, 0, length);
        }
        if (isSplitable(fs, path)) {
          long blockSize = file.getBlockSize(); //获取block大小,默认128M
          //计算切片大小,函数在代码下方
          long splitSize = computeSplitSize(goalSize, minSize, blockSize); 

          long bytesRemaining = length;
          //变量设定了溢出系数SPLIT_SLOP为1.1
          //每次切完判断是否大于块的1.1倍,小于等于为一块,大于则再切分
          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.now(TimeUnit.MILLISECONDS));
    }
    return splits.toArray(new FileSplit[splits.size()]); //集合转为数组
  }

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

这里涉及到几个关键的计算变量,由公式Math.max(minSize,Math.min(goalSize, blockSize))体现:

  • goalSize:输入总大小与如此numSplits比值,如没有设置则按默认为文件总大小,numSplits通过参数mapreduce.job.maps可以设定(故想增加map个数,可以通过设置该参数为一个较大的值)。
  • blockSize:HDFS的块大小,默认128M。
  • minSize:1与SPLIT_MINSIZE取最大的一个,通过参数mapreduce.input.fileinutformat.split.minsize可以设定(故想减少map个数,可以同个设置该参数为一个较大的值)

1.上面参数hadoop版本1.x与2.x有区别。
2.通过公式可知只能通过参数设置控制增加或减少map数量,无法具体指定个数。
3.reduce个数默认为1个,也可以通过参数mapreduce.job.reduces设置。
4.上面源码getSplits调用后并不实际做文件切分,而是返回一个列表记录要切分的信息,后续提交到Yarn上处理。

三、实验验证

提取hadoop目录hadoop/etc/hadoop下xml文件共计9份上传到HDFS,做字母计数计算,测试是否启动对应9个map任务。

#在HDFS上新建input文档,把hadoop/etc/hadoop目录下xml文件导入
hdfs dfs -mkdir input
hdfs dfs -put /usr/local/hadoop/etc/hadoop/*.xml input
hadoop jar /usr/local/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar grep input output 'dfs[a-z.]+'

在这里插入图片描述

由于每个文件不超过block块128M,可以看到启动了9个Map任务,1个Reduce任务。
在这里插入图片描述
在这里插入图片描述
Referance

blog.csdn.net/sun_0128/article/details/107115914


学习交流,有任何问题还请随时评论指出交流。

posted @ 2021-03-16 20:02  Rango_lhl  阅读(322)  评论(0编辑  收藏  举报