Hadoop MapReduce中如何處理跨行Block和UnputSplit

來源:互聯網
上載者:User
Hadoop的初學者經常會疑惑這樣兩個問題:1.Hadoop的一個Block預設是64M,那麼對於一個記錄行形式的文本,會不會造成一行記錄被分到兩個Block當中?2.在把檔案從Block中讀取出來進行切分時,會不會造成一行記錄被分成兩個InputSplit,如果被分成兩個InputSplit,這樣一個InputSplit裡面就有一行不完整的資料,那麼處理這個InputSplit的Mapper會不會得出不正確的結果?

對於上面的兩個問題,首先要明確兩個概念:Block和InputSplit

      1. block是hdfs隱藏檔的單位(預設是64M);
      2. InputSplit是MapReduce對檔案進行處理和運算的輸入單位,只是一個邏輯概念,每個InputSplit並沒有對檔案實際的切割,只是記錄了要處理的資料的位置(包括檔案的path和hosts)和長度(由start和length決定)。

因此,以行記錄形式的文本,還真可能存在一行記錄被劃分到不同的Block,甚至不同的DataNode上去。通過分析FileInputFormat裡面的getSplits方法,可以得出,某一行記錄同樣也可能被劃分到不同的InputSplit。

    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();          long length = file.getLen();          if (length != 0) {            FileSystem fs = path.getFileSystem(job.getConfiguration());            BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);            if (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(makeSplit(path, length-bytesRemaining, splitSize,                                         blkLocations[blkIndex].getHosts()));                bytesRemaining -= splitSize;              }                    if (bytesRemaining != 0) {                splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,                           blkLocations[blkLocations.length-1].getHosts()));              }            } else { // not splitable              splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts()));            }          } 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());        LOG.debug("Total # of splits: " + splits.size());        return splits;      }  

 從上面的代碼可以看出,對檔案進行切分其實很簡單:擷取檔案在HDFS上的路徑和Block資訊,然後根據splitSize

對檔案進行切分,splitSize = computeSplitSize(blockSize, minSize, maxSize);blockSize,minSize,maxSize都可以配置,預設splitSize 就等於blockSize的預設值(64m)。

FileInputFormat對檔案的切分是嚴格按照位移量來的,因此一行記錄比較長的話,其可能被切分到不同的InputSplit。但這並不會對Map造成影響,儘管一行記錄可能被拆分到不同的InputSplit,但是與FileInputFormat關聯的RecordReader被設計的足夠健壯,當一行記錄跨InputSplit時,其能夠到讀取不同的InputSplit,直到把這一行記錄讀取完成,在Hadoop裡,記錄行形式的文本,通常採用預設的TextInputFormat,TextInputFormat關聯的是LineRecordReader,下面我們來看看LineRecordReader的的nextKeyValue方法裡讀取檔案的代碼:

    while (getFilePosition() <= end) {        newSize = in.readLine(value, maxLineLength,            Math.max(maxBytesToConsume(pos), maxLineLength));        if (newSize == 0) {          break;        }  

 其讀取檔案是通過LineReader(in就是一個LineReader執行個體)的readLine方法完成的:

    public int readLine(Text str, int maxLineLength,                          int maxBytesToConsume) throws IOException {        if (this.recordDelimiterBytes != null) {          return readCustomLine(str, maxLineLength, maxBytesToConsume);        } else {          return readDefaultLine(str, maxLineLength, maxBytesToConsume);        }      }            /**      * Read a line terminated by one of CR, LF, or CRLF.      */      private int readDefaultLine(Text str, int maxLineLength, int maxBytesToConsume)      throws IOException {        str.clear();        int txtLength = 0; //tracks str.getLength(), as an optimization        int newlineLength = 0; //length of terminating newline        boolean prevCharCR = false; //true of prev char was CR        long bytesConsumed = 0;        do {          int startPosn = bufferPosn; //starting from where we left off the last time          if (bufferPosn >= bufferLength) {            startPosn = bufferPosn = 0;            if (prevCharCR)              ++bytesConsumed; //account for CR from previous read            bufferLength = in.read(buffer);            if (bufferLength <= 0)              break; // EOF          }          for (; bufferPosn < bufferLength; ++bufferPosn) { //search for newline            if (buffer[bufferPosn] == LF) {              newlineLength = (prevCharCR) ? 2 : 1;              ++bufferPosn; // at next invocation proceed from following byte              break;            }            if (prevCharCR) { //CR + notLF, we are at notLF              newlineLength = 1;              break;            }            prevCharCR = (buffer[bufferPosn] == CR);          }          int readLength = bufferPosn - startPosn;          if (prevCharCR && newlineLength == 0)            --readLength; //CR at the end of the buffer          bytesConsumed += readLength;          int appendLength = readLength - newlineLength;          if (appendLength > maxLineLength - txtLength) {            appendLength = maxLineLength - txtLength;          }          if (appendLength > 0) {            str.append(buffer, startPosn, appendLength);            txtLength += appendLength;          }        } while (newlineLength == 0 && bytesConsumed < maxBytesToConsume);   <span style="color: #ff0000;">//①</span>              if (bytesConsumed > (long)Integer.MAX_VALUE)          throw new IOException("Too many bytes before newline: " + bytesConsumed);            return (int)bytesConsumed;      }  

 我們分析下readDefaultLine方法,do-while迴圈體主要是讀取檔案,然後遍曆讀取的內容,找到預設的分行符號就終止迴圈。前面說,對於跨InputSplit的行,LineRecordReader會自動跨InputSplit去讀取。這就體現在上述代碼的While迴圈的終止條件上:

while (newlineLength == 0 && bytesConsumed < maxBytesToConsume);

newlineLength==0則以為一次do-while迴圈中讀取的內容中沒有遇到分行符號,因maxBytesToConsume的預設值為Integer.MAX_VALUE,所以如果讀取的內容沒有遇到分行符號,則會一直讀取下去,知道讀取的內容超過maxBytesToConsume。這樣的出來方式,解決了一行記錄跨InputSplit的讀取問題,同樣也會造成下面兩個疑問:

1.既然在LineReader讀取方法裡面沒有對考慮InputSplit的end進行處理,難道讀取一個InputSplit的時候,會這樣無限的讀取下去嗎?

2.如果一行記錄L跨越了A,B兩個InputSplit,讀A的時候已經讀取了跨越A,B的這條記錄L,那麼對B這個InputSplit讀取的時候,如果做到不讀取L這條記錄在B中的部分呢?

為瞭解決這兩個問題,Hadoop通過下面的代碼來做到:LineRecordReader的nextKeyValue方法。

    public boolean nextKeyValue() throws IOException {        if (key == null) {          key = new LongWritable();        }        key.set(pos);        if (value == null) {          value = new Text();        }        int newSize = 0;        // We always read one extra line, which lies outside the upper        // split limit i.e. (end - 1)        while (getFilePosition() <= end) {        <span style="color: #ff0000;"> //②</span>          newSize = in.readLine(value, maxLineLength,              Math.max(maxBytesToConsume(pos), maxLineLength));          if (newSize == 0) {            break;          }          pos += newSize;          inputByteCounter.increment(newSize);          if (newSize < maxLineLength) {            break;          }                // line too long. try again          LOG.info("Skipped line of size " + newSize + " at pos " +                    (pos - newSize));        }        if (newSize == 0) {          key = null;          value = null;          return false;        } else {          return true;        }      }  

    通過代碼②處得While條件,就保證了InputSplit讀取邊界的問題,如果存在跨InputSplit的記錄,也只好跨InputSplit讀取一次。

     再來看LineRecordReader的initialize方法:

    // If this is not the first split, we always throw away first record      // because we always (except the last split) read one extra line in      // next() method.      if (start != 0) {        start += in.readLine(new Text(), 0, maxBytesToConsume(start));      }      this.pos = start;  

    如果不是第一InputSplit,則在讀取的時候,LineRecordReader會自動忽略掉第一個分行符號之前的所有內容,這樣就不存在重讀讀取的問題。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.