1. 程式人生 > >Hadoop中分片split的原理解析

Hadoop中分片split的原理解析

一、定義

1、block:block是物理切塊,在檔案上傳到HDFS檔案系統後,對大文將以每128MB的大小切分若干,存放在不同的DataNode上;

2、split:split是邏輯切片,在mapreduce中的map task開始之前,將檔案按照指定的大小切割成若干個部分,每一部分稱為一個split,預設是split的大小與block的大小相等,均為128MB。

注意:在hadoop1.x版本中,block預設的大小為64MB,在hadoop2.x版本修改成了128MB。

二、引數設定

1、block預設配置在hdfs-default.xml中(hadoop-hdfs-x.x.x.jar目錄下)

<property>
  <name>dfs.blocksize</name>
  <value>134217728</value>
  <description>
      預設block的大小引數配置以位元組為單位(例如134217728,128 MB)
      也可以使用如128k,512m,1g等為單位(不區分大小寫)
  </description>
</property>

注意:預設配置就是最佳實踐

2、split大小由minSize、minSize、blocksize決定,以下是預設配置情況下的

  • long minSize = 1
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));

  protected long getFormatMinSplitSize() {
    return 1;
  }
  
   public static long getMinSplitSize(JobContext job) {
   // SPLIT_MINSIZE預設值為0,因此getMinSplitSize(JobContext job)返回值為1
    return job.getConfiguration().getLong(SPLIT_MINSIZE, 1L);
  }
  • long maxSize = MAX_VALUE
  long maxSize = getMaxSplitSize(job);
	
	// 返回預設值MAX_VALUE
    public static long getMaxSplitSize(JobContext context) {
    return context.getConfiguration().getLong(SPLIT_MAXSIZE, 
                                              Long.MAX_VALUE);
  }
  
  // 獲取配置檔案引數值
  public long getLong(String name, long defaultValue) {
  // 使用者為自定義引數屬性
    String valueString = getTrimmed(name);
    if (valueString == null)
	// 返回系統預設值,即public static final long MAX_VALUE
      return defaultValue;
    String hexString = getHexDigits(valueString);
    if (hexString != null) {
      return Long.parseLong(hexString, 16);
    }
    return Long.parseLong(valueString);
  }
  • blocksize = 134217728

由上面三個引數就可以計算出分片大小了,也能得到如下結論:

Split與block的對應關係可能是多對一,預設是一對一

在mapreduce的FileInputFormat類中的getSplits() 方法對檔案進行split,演算法如下:

Math.max(minSize,Math.min(maxSize, blockSize)),其中maxSize是取得longValueMax的值

1.如果blockSize小於maxSize && blockSize 大於 minSize之間,那麼split就是blockSize(一對一);

2.如果blockSize小於maxSize && blockSize 小於 minSize之間,那麼split就是minSize;

3.如果blockSize大於maxSize && maxSize   大於 minSize之間,那麼split就是maxSize(多對一);

4.如果blockSize大於maxSize && maxSize   小於 minSize之間,那麼split就是maxSize(不存在這種關係)。

在優化過程中,若想調整split大小控制map task的數量,原則如下:

檔案大小不變,minsize大小預設,增加map task數量,減小maxSize,則split減小

檔案大小不變,maxSize大小預設,減小map task數量,增大minSize,則split增大

注意:split大小如何調整,split只能是一個檔案的分片,不能讓多個小檔案“劃入”一個split中

三、分裝切片物件的主要原始碼

檔案的最後一個分片可能會超過128MB,由於常量SPLIT_SLOP = 1.1決定,大小範圍在:0MB < lastSplit < 128+12.8 MB

  /** 
   * 生成切片物件集合,InputSplit物件封裝.
   * @param job the job context
   * @throws IOException
   */

  public List<InputSplit> getSplits(JobContext job) throws IOException {
    StopWatch sw = new StopWatch().start();
	// 分片最小值,getFormatMinSplitSize()返回值為1,getMinSplitSize(job)返回值為1
    long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
	// 分片最大值是常量:public static final long MAX_VALUE
    long maxSize = getMaxSplitSize(job);

    // 生成分片列表
    List<InputSplit> splits = new ArrayList<InputSplit>();
	// 判斷檔案是否可用
    List<FileStatus> files = listStatus(job);
	// 獲取檔案路徑、大小
    for (FileStatus file: files) {
      Path path = file.getPath();
      long length = file.getLen();
      if (length != 0) {
        BlockLocation[] blkLocations;
		// 獲取block位置資訊
        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)) {
		// 獲取block大小,預設128MB
          long blockSize = file.getBlockSize();
		  // 計算split大小,預設128MB
          long splitSize = computeSplitSize(blockSize, minSize, maxSize);
			// 檔案大小
          long bytesRemaining = length;
		  // 當檔案大小滿足bytesRemaining/128 > 1.1 進行切片
		  // private static final double SPLIT_SLOP = 1.1;
          while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
			// 記錄block索引位置
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                        blkLocations[blkIndex].getHosts(),
                        blkLocations[blkIndex].getCachedHosts()));
			// 檔案剩餘大小
            bytesRemaining -= splitSize;
          }
			// 判斷檔案是否切片完成
          if (bytesRemaining != 0) {
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
                       blkLocations[blkIndex].getHosts(),
                       blkLocations[blkIndex].getCachedHosts()));
          }
        } else { // not splitable
          splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
                      blkLocations[0].getCachedHosts()));
        }
      } else { // 檔案大小不滿足切片要求
        //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 long computeSplitSize(long blockSize, long minSize,
                                  long maxSize) {
    return Math.max(minSize, Math.min(maxSize, blockSize));
  }

   // 封裝分片資訊,檔案路徑,開始位置,檔案大小,主機名,儲存該block的所有主機名列表
  protected FileSplit makeSplit(Path file, long start, long length, String[] hosts, 
								String[] inMemoryHosts) {
    return new FileSplit(file, start, length, hosts, inMemoryHosts);
  }