InputFormat&OutputFormat

來源:互聯網
上載者:User

標籤:hadoop   framework   架構   泛型   源碼   

本文的主要目的是從源碼層級講解Hadoop中InputFormat和OutputFormat部分,首先簡介InputFormat和OutputFormat,然後介紹兩個重要的組件,RecordWriter和RecordReader,再以FileInputFormat和FileOutputFormat為例,介紹一組InputFormat和OutputFormat的實現細節,最後以SqoopInputFormat和SqoopOutputFormat為例,體會一下InputFormat和OutputFormat的靈活應用。

InputFormat和OutputFormat簡介

InputFormat規定了MR架構,如何解析輸入檔案,比如說TextOutputFormat內部實現了一個LineRecordReader,從LineRecordReader#nextKeyValue方法可以看出,它的key是將要讀的這一行資料的起始位元組位置,value是這一行的內容。

OutputFormat規定了MR架構,最後輸出的檔案的內容格式,比如說SequenceFileOutputFormat,其getRecordWriter方法返回一個RecordWriter的內部類,具體的操作在SequenceFile類裡write相關的方法中。SequenceFile的具體實現只從類來看比價複雜,其一條記錄可以簡單理解成如下的格式[record length][key length][key][value]。

RecordReader和RecordWriter簡介

上邊提到了RecordReader和RecordWriter,這裡簡單介紹一下,RecordWriter和RecordReader在MR架構每一個輸入輸出的地方用到,讀寫操作調用的都是RecordReader和RecordWriter提供的介面,比如說我們在Mapper#map和Reducer#reduce的結束通常都會寫這樣一行代碼context.write(key,value),這行代碼實際調用的就是RecordWriter#write方法,RecordReader是MR架構調用的。

一個InputFormat對應一個RecordReader,一個OutputFormat對應一個RecordWriter。這樣在後邊的DIY的時候,也知道應該寫些什麼了。

FileInputFormat和FileOutputFormat

這裡以FileInputFormat,FileOutputFormat和其對應的子類TextInputFormat和TextOutputFormat為例,分析一套InputFormat和OutputFormat的具體實現。

1.      InputFormat

InputFormat抽象類別中有兩個方法:

InputFormat#getSplits(JobContext context)

對輸入檔案進行邏輯上的分區

InputFormat#createRecordReader(InputSplitsplit,TaskAttemptContext context)

返回一個RecordReader對象,該對象可將輸入的InputSplit解析成若干個key/value對。

2.      FileInputFormat

所有需要處理檔案的InputFormat的基類,從其實現可以看出,FileInputFormat主要是實現InputFormat#getSplit(JobContext context)方法,由於每個子類對應不同的輸入格式,所以解析InputSplit的方法InputFormat#createRecordReader(InputSplit split,TaskAttemptContextcontext)由各個子類自己實現。

這裡我們先分析FileInputFormat#getSplit(JobContext context)方法。

 

/**  * Generate the list of files and make them into FileSplits.  * @param job the job context  * @throws IOException  */ public List<InputSplit> getSplits(JobContext job) throwsIOException {   long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));   long maxSize = getMaxSplitSize(job); // generatesplits   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);//擷取一個檔案的所有的Block的帶位置的資訊        if (isSplitable(job, path)) {          long blockSize = file.getBlockSize();          long splitSize =computeSplitSize(blockSize, minSize, maxSize);//計算一個FileSplit的大小,計算過程如下,Math.max(minSize,Math.min(maxSize, blockSize))          long bytesRemaining =length;                     //這個while迴圈就是根據上邊的準備的資訊,不停的讀檔案,產生FileSplit,一直到檔案末尾。          while (((double)bytesRemaining)/splitSize > SPLIT_SLOP) {            int blkIndex =getBlockIndex(blkLocations, length-bytesRemaining);            splits.add(makeSplit(path,length-bytesRemaining, splitSize,                                    blkLocations[blkIndex].getHosts()));            bytesRemaining -= splitSize;          }                     //處理最後不足splitSize大小的資料          if (bytesRemaining != 0) {           int blkIndex =getBlockIndex(blkLocations, length-bytesRemaining);            splits.add(makeSplit(path,length-bytesRemaining, bytesRemaining,                      blkLocations[blkIndex].getHosts()));          }        } else { // not splitable不能分割的話,檔案只有一塊blkLocations[0]就是這一塊          splits.add(makeSplit(path, 0, length,blkLocations[0].getHosts()));        }     } else {        //Create empty hosts array for zerolength 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;  }

3.      TextInputFormat

TextInputFormat直接使用了FileInputFormat的getSplits方法,自己實現了createRecordReader方法。

TextInputFormat#createRecordReader方法邏輯比較簡單,最後返回一個LineRecordReader對象,下面主要看這個方法LineRecordReader#nextKeyValue。

public boolean nextKeyValue() throws IOException {    if (key == null) {      key = newLongWritable();    }    key.set(pos);    if (value == null) {      value = new Text();    }    int newSize = 0;    // We always read oneextra line, which lies outside the upper    // split limit i.e. (end -1)while (getFilePosition() <= end) {//這一句就是讀資料的地方了      newSize =in.readLine(value, maxLineLength,         Math.max(maxBytesToConsume(pos), maxLineLength));      if (newSize == 0) {        break;      }      pos += newSize;      if (newSize <maxLineLength) {        break;      }       // line too long. tryagain      LOG.info("Skippedline of size " + newSize + " at pos " +               (pos -newSize));    }    if (newSize == 0) {      key = null;      value = null;      return false;    } else {      return true;    }  }

4.      OutputFormat

OutputFormat# getRecordWriter (TaskAttemptContext context)

返回一個RecordWriter對象

OutputFormat# checkOutputSpecs (JobContext context)

判斷輸出檔案存不存在,寫過MR程式的人應該很熟悉了,如果輸出路徑已經存在話,會拋出一個Output directory " + outDir + " already exists"錯誤,就是這個方法拋出的。

OutputFormat# getOutputCommitter (TaskAttemptContext context)

MR運行過程中,會產生很多中間檔案,比如Mapper的輸出,推測式執行Task時產生的檔案等等,這個方法負責在任務執行完成後,處理這些中間檔案。順便說下,OutputCommitter對象裡的方法都是回調方法,MR自動調用。

5.      FileOutputFormat

FileOutputFormat對上邊提到的3個方法中的後兩個提供了通用的實現,OutputFormat# getRecordWriter (TaskAttemptContext context)方法需要子類自己實現。

public void checkOutputSpecs(JobContext job                              ) throws FileAlreadyExistsException, IOException{    // Ensure that the outputdirectory is set and not already there    Path outDir =getOutputPath(job);    if (outDir == null) {      throw newInvalidJobConfException("Output directory not set.");    }     // get delegation tokenfor outDir's file system   TokenCache.obtainTokensForNamenodes(job.getCredentials(),        new Path[] { outDir },job.getConfiguration());     if(outDir.getFileSystem(job.getConfiguration()).exists(outDir)) {      throw newFileAlreadyExistsException("Output directory " + outDir +                                          " already exists");    }  } public synchronized     OutputCommittergetOutputCommitter(TaskAttemptContext context                                        )throws IOException {    if (committer == null) {      Path output =getOutputPath(context);      committer = newFileOutputCommitter(output, context);    }    return committer;  }

6.      TextOutputFormat

TextOutputFormat直接使用了FileInputFormat的getSplits方法,自己實現了createRecordReader方法。

TextOutputFormat#createRecordWriter方法邏輯比較簡單,最後返回一個LineRecordWriter對象,下面主要看這個方法LineRecordReader#write(K key,V Value)。

public synchronized void write(K key, V value)      throws IOException {       boolean nullKey = key ==null || key instanceof NullWritable;      boolean nullValue =value == null || value instanceof NullWritable;      if (nullKey &&nullValue) {        return;      }          //這個地方是最主要的邏輯      if (!nullKey) {        writeObject(key);//寫出key      }      if (!(nullKey ||nullValue)) {       out.write(keyValueSeparator);//寫出key/value的分隔字元      }      if (!nullValue) {        writeObject(value);//寫出value      }      out.write(newline);//寫出行結束符}

SqoopInputFormat和SqoopOutputFormat

下面的分析基於Sqoop1.99.3

在Sqoop中,InputFormat和OutputFormat的子類有3個,分別是:

public class SqoopInputFormat extendsInputFormat<SqoopSplit, NullWritable>public class SqoopFileOutputFormat extendsFileOutputFormat<Data, NullWritable>public class SqoopNullOutputFormat extendsOutputFormat<Data, NullWritable>

下面一個個分析:

1.      SqoopInputFormat

從泛型傳入的類型可以看出,SqoopInputFormat的key是SqoopSplit,value是NullWritable。直接把整個InputSplit作為key,傳到Mapper中,SqoopInputFormat不對InputSplit做任何的解析操作。

2.      SqoopFileOutputFormat

從泛型傳入的類型來看,跟SqoopInputFormat類似,只是用KEY部分儲存資訊。SqoopFileOutputFormat只重寫了父類FileOutputFormat的getRecordWriter方法和getOutputCommitter方法,checkOutputSpec方法使用的父類的。

getRecordWriter方法調用SqoopOutputFormatLoadExecutor#getRecordWriter,這個方法在返回一個SqoopRecordWriter的同時,開啟一個消費者線程,SqoopRecordWriter是生產者線程。[後面接著分析Sqoop源碼時細說]

3.      SqoopNullOutputFormat

SqoopNullOutputFormat將OutputFormat的3個方法都重寫了。SqoopNullOutputFormat#getRecordWriter方法同樣是調用SqoopOutputFormatLoadExecutor#getRecordWriter。

InputFormat&OutputFormat

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.