Hadoop MR 數據切片與輸入格式化

切片

  • MapReduce 執行中是以mapTask 爲單位對數據進行處理,

  • mapTask 的個數與操作數據段是由切片決定的

  • 切片是在單個文件的基礎上通過一些機制設定的

  • 默認 MapReduce 使用 TextInputFormat 對數據進行讀取分片, 切片大小默認爲 block 大小

  • 切片過大會造成按個 mapTask 執行時間長,不能充分利用 hadoop 集羣 dataNode 多節點的特性; 切片過小會造成 由於啓動 mapTask 本身需要一定的時間, 而實際處理數據時間很短暫, 造成的大量啓動 mapTask 造成性能低下

  • 大量小文件會對 MapReduce 造成性能影響

在這裏插入圖片描述

默認切片等於 block 大小, 當單個節點存儲數據等於 block 時(源數據>=block, 分片存儲在單個或多個節點), 一個節點會對一個文件生成一個 mapTask 執行數據操作,並且不會有數據傳輸的消耗

在這裏插入圖片描述

假設切片爲 100M, 小於 block, 那麼當單個節點存儲數據等於 block 時(源數據>=block, 分片存儲在單個或多個節點), mapTask 會在不同的dataNode 節點讀取數據, 造成數據傳輸過程中的性能損耗

  • 切片過程
    1. 程序找到數據存儲的目錄
    2. 開始遍歷處理目錄下的每個文件
    3. 遍歷到第一個文件 f.txt
      1. 獲取文件大小 fs.size(f.txt)
      2. 計算切片大小 computeSplitSize(Math.max(minSize, Math.min(maxSize,blocksize)))=blocksize=128M
      3. 默認切片大小=blocksize
      4. 開始切片, 0-128M, 128-256M, 256-300M(每次切片時,都要判斷切完剩餘部分是否大於block 的 1.1 倍, 不大於就劃分爲 1 塊)
      5. 建切片信息記錄到規劃文件中(記錄每個切片元數據:起止位置)
    4. 提交切片規劃文件到 YARN 上, YARN 上的 MrAppMaster根據規劃文件計算 mapTask 調度資源執行

輸入格式

MapReduce 的切片過程是在 FileInputFormat 中實現的, FileInputFormat官方提供了多個實現類* *

  • TextInputFormat

    TextInputFormat是默認的 InputFormat. 每條記錄是一行輸入, 建是 LongWritable 類型, 是當前數據讀取起始字節偏移量,值是當前數據的一行數據(不包含結束行符:回車/換行)

    hello!\n
    hello, zcz!\n
    hello world!\n
    

    上面數據會被劃分爲3條記錄

    key  value
    0    hello!
    6    hello, zcz!
    17   hello world!
    

    Configuration可以通過設置LineRecordReader.MAX_LINE_LENGTH屬性, 限制單行數據讀取最大字節數, 若單行超過限制,表示數據異常, 不處理此行;

    conf.set(LineRecordReader.MAX_LINE_LENGTH, 2147483647);
    

    **注意: **文件是分塊存儲在不同節點的, 有可能一塊數據的最後一行分在兩個節點存儲, 那麼 mapTask 讀取這一行時, 會存在跨節點讀取數據, (雖然這種性能不會損耗很大, 但應注意, 一行數據非常大時, 可能傳輸數據就會很大消耗)

  • KeyValueTextInputFormat

    按行讀取數據, 讀取的數據可以根據指定分隔符, 讀取出一行數據由這個分隔符分割出的第一個字符串爲 key, 此行剩餘部分爲 value, 可以通過KeyValueLineRecordReader.KEY_VALUE_SEPERATOR指定分隔符

    java		hello java!
    scala		hello scala!
    python		hello python!
    ruby		hello ruby!
    golang		hello golang!
    php			hello php!
    
    conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, "\t");
    

    讀取的內容爲

    key			value
    java		hello java!
    scala		hello scala!
    python		hello python!
    ruby		hello ruby!
    golang		hello golang!
    php			hello php!
    
  • NLineInputFormat

    按照指定行數讀取數據, 讀到的數據 key 是字節偏移量, value 是讀取到的所有行數據

    可以通過NLineInputFormat.LINES_PER_MAP設置讀取的行數限制, 默認 1 行

    conf.set(NLineInputFormat.LINES_PER_MAP, 1);
    
  • SequenceFileInputFormat

    讀取 SequenceFile 類型數據, key 和 value 由 SequenceFile 內部數據決定,它是存儲二進制數據的, 並且是可壓縮的, 內部可以維護 key-value. 後面通過學習 SequenceFile 補充;

    • SequenceFileAsTextInputFormat

      將讀取SequenceFile的 key-value 轉爲 Text 類型

    • SequenceFileAsTextInputFormat

      將讀取SequenceFile的 key-value 轉爲 BytesWritable 類型

  • FixLengthInputFormat

    用戶從文件中讀取固定寬度的二進制記錄, 默認這些數據沒有用分隔符分開,通過設置FixLengthInputFormat.FIXED_RECORD_LENGTH設置每個記錄的大小

  • CombineTextInputFormat

    大量小文件情況下, 會對每一個小文件劃爲一個切片,導致每個小文件一個 mapTask, 造成 MapReduce 性能及其低下, 通過 CombineTextInputFormat 可以將多個小文件劃分到一個切片, 提高 MapReduce 單次處理的文件數據量, 減少 MapReduce 啓動停止的開銷

    CombineTextInputFormat.setMaxInputSplitSize(job,102400000);// 100M
    
    file1.txt    	20M				20M					(20+50+60)M
    file2.txt		50M				50M					
    file2.txt		120M			60M
    								60M					(60+70)M
    file2.txt		70M				70M
    file2.txt		70M				50M					(50+60)M
    file2.txt		120M			60M
    								60M					(60+80)M
    file2.txt		80M				80M
    
    1. 輸入源目錄下有多個文件讀取文件大小
    2. 判斷文件大小是否大於分片大小, 如果大於, 將文件劃分爲以filesize/(filesize/splitsize+(filesize%splitsize>0?1:0))大小相等的多個分片 例如 120>100 劃分爲兩個 60
    3. 將劃分後的分片組合, 判斷第一個分片是否大於 splitsize, 不大於, 直接+上後一個分片的大小,此時如果大於 splitsize, 則劃分爲一個分片, 下面再劃分新的分片,
    4. 上面最開始的 7 個文件最終劃分爲四個分片 130M 130M 110M 140M
  • MultipleInputs 多個輸入, 爲 MapReduce 操作多個 InputFormat 和 mapper

  • DBInputFormat 使用 jdbc 讀取關係型數據庫內的數據

自定義輸入格式

  1. 繼承 FileInputFormat抽象類(讀文本文件, ), 必須實現一個必須實現的createRecordReader方法, 創建RecordReader

    也可以繼承其它類實現不同的數據讀取: CompositeInputFormat, DBInputFormat, ComposableInputFormat等

  2. 可以選擇重寫 FileInputFormat 其它方法, 包括是否分片, 和如何分片邏輯

  3. 自定義RecordReader, 內部爲讀取每一組 key- value 的邏輯

/**
 * <h3>study-all</h3>
 *
 * <p></p>
 *
 * @Author zcz
 * @Date 2020-04-04 19:49
 */
public class CustomerInputFormat extends FileInputFormat {

    @Override
    public RecordReader createRecordReader(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        CustomerRecordReader customerRecordReader = new CustomerRecordReader();
        customerRecordReader.initialize(inputSplit, taskAttemptContext);
        return customerRecordReader;
    }
}
/**
 * <h3>study-all</h3>
 *
 * <p></p>
 *
 * @Author zcz
 * @Date 2020-04-04 19:53
 */
public class CustomerRecordReader extends RecordReader {

    //根據讀取數據源類型 這裏可以使用多種 split
    //CombineFileSplit
    //CompositeInputSplit
    //DBInputSplit
    //FileSplit
    private FileSplit split;
    private TaskAttemptContext context;
    //key和 value 都是事先 writable 的可序列化的對象, 根據具體業務需求, 判定 mapper 輸入的 key-value 類型
    private Object key;
    private Object value;

    //初始化切片和上下文, 可以在這裏對數據進行校驗  初始化, 等邏輯
    @Override
    public void initialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        this.split = (FileSplit)inputSplit;
        this.context = taskAttemptContext;
    }

    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {
        //split 拿到的是當前整個分區數據, 根據具體業務, 讀取分區數據內容,
        //根據實際業務邏輯從 split 中讀數據, 記錄讀出數據的位置,
        //判定讀取的數據還沒有讀取完, 返回 true 繼續讀
        //判定讀取的數據已經讀完了, 返回 false

        return false;
    }

    @Override
    public Object getCurrentKey() throws IOException, InterruptedException {
        //返回本次的 key 對應 mapper 輸入的 key
        return key;
    }

    @Override
    public Object getCurrentValue() throws IOException, InterruptedException {
        //返回本地的 value 對應 mapper 輸入的 value
        return value;
    }

    @Override
    public float getProgress() throws IOException, InterruptedException {
        //返回當前讀取的進度
        return 0;
    }

    @Override
    public void close() throws IOException {

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章