MapReduce知識點整理

MapReduce知識點整理

基於版本:Hadoop 2.7.2

序列化

Q: 爲什麼Hadoop不使用Java自帶的序列化?
A: Java自帶的序列化框架過於重量級(附帶很多額外信息:校驗信息、Header、繼承體系等),網絡傳輸效率低,所以Hadoop自己實現了序列化機制(Writable接口).

自定義可序列化類

  1. 實現Writable接口
  2. 反序列化用到反射,需要調用無參數構造方法,因此自定義的可序列化類需要有無參數構造方法
  3. 重新write方法(序列化)和readFields方法(反序列化),注意序列化和反序列化時字段順序需要保持一致
  4. Hadoop的shuffle過程會對鍵排序,因此如果需要自定義的可序列化類對象作爲鍵,還需要實現WritableComparable接口
  5. 如果對象需要作爲reduce的輸出寫入文件,需要重寫toString方法實現其字符串形式的表示,一般採用\t將字段分開的形式,便於之後的MapReduce任務讀取

InputFormat

InputFormat

抽象類InputFormat是輸入格式的基類,主要實現了兩個功能:

  • 將輸入文件切片
  • 從切分讀入鍵值對序列

FileInputFormat

  • FileInputFormat是針對文件輸入的抽象類,主要處理文件切片邏輯
  • 按輸入文件長度切片,默認的切片大小等於BlockSize,對於每個輸入文件單獨切分而不是合併切片
  • splitSize = Math.max(minSize, Math.min(maxSize, blockSize))
  • 通過設置mapreduce.input.fileinputformat.split.minsize(默認值爲1)大於BlockSize,讓切片變大
  • 通過設置mapreduce.input.fileinputformat.split.maxsize(默認值爲Long.MAX_VALUE)小於BlockSize,讓切片變小
  • 細節:將文件切片時,每次判斷剩餘文件大小是否大於切片大小的1.1倍,如果是,則新建切片

TextInputFormat

  • 默認的輸入格式實現類
  • 按行讀取文件
  • 鍵:存儲在整個文件中的起始字節偏移量,LongWritable類型
  • 值:本行內容,不包含換行符和回車符,Text類型

CombineTextInputFormat

  • 功能:合併小文件
  • 兩階段切片機制:(1)虛擬存儲過程;(2)切片過程
  • CombineTextInputFormat.setMaxInputSplitSize(job, MAX_SIZE)將虛擬存儲切片最大的大小設置爲MAX_SIZE
  • CombineTextInputFormat.setMaxInputSplitSize(job, MIN_SIZE)將虛擬存儲切片最小的大小設置爲MIN_SIZE

OutputFormat

抽象類OutputFormat是Reduce輸出的基類

TextOutputFormat

默認的輸出格式,把每行記錄作爲文本行。內部調用toString方法把鍵和值轉化爲字符串

SequenceFileOutputFormat

當該MapReduce任務的輸出被用於下一個MapReduce任務的輸入時使用,下一個MapReduce任務使用SequenceFileInputFormat作爲輸入格式。優點是合併了小文件,存儲格式緊湊,便於壓縮。

自定義OutputFormat

  • 爲了控制輸出文件的路徑和輸出格式,可以自定義OutputFormat. 例如可以用於MapReduce任務結果寫入MySQL, redis等存儲中
  • 自定義一個類繼承FileOutputFormat
  • 自定義一個類繼承RecordWriter,重寫write方法

MapReduce工作流程

MapTask工作機制

  1. Read階段
  2. Map階段
  3. Collect階段
    • 把計算好的<k, v>對以及元信息放在環形緩衝區,所謂環形緩衝區實際只是一個字節數組,用equator標明瞭環的起始位置
    • <k, v>對從equator按照索引遞增方向存儲
    • 元信息(partition, keystart, valstart, vallen, kvindex)從equator按照索引遞減方向存儲
  4. 溢寫階段:當環形緩衝區的內容佔緩衝區大小80%時,會把環形緩衝區的內容快速排序後寫入磁盤臨時文件(如果有combiner,則寫入磁盤前會先經過combiner),當MapTask的所有數據處理完成後,對臨時文件中的內容進行歸併,排序後的內容寫入磁盤(如果有combiner,則寫入磁盤前會再次經過combiner)
    • Q: 爲什麼不等環形緩衝區滿再溢寫? A: 會出現寫入環形緩衝區阻塞
    • Q: 爲什麼把<k, v>對與元信息放在同一個buffer? A: 提高buffer利用率,減少溢寫次數

關於MapTask的源碼級別的分析可以參考這篇博客:MapReduce源碼解析–Map解析

ReduceTask工作機制

  1. Copy階段
  2. Sort階段
  3. Reduce階段

關於ReduceTask的源碼級別的分析可以參考這篇博客:MapReduce源碼解析–Reduce解析

Map/Reduce任務的並行度

  • map任務並行度等於切片數,默認切片大小等於BlockSize
  • reduce任務並行度由用戶指定參數numReduceTasks決定,等於輸出文件的個數
  • reduce任務的並行度默認爲1
  • 如果不需要reduce任務,則將numReduceTasks設爲0,此時map任務的輸出直接寫入輸出路徑
  • 如果自定義分區數大於1而numReduceTasks爲1,則不會進行分區

Partition

  • 默認:HashPartioner
public class HashPartitioner<K, V> extends Partitioner<K, V> {
    public int getPartition(K key, V value, int numReduceTAsks) {
        return (key.hashCode() & Integer.MAX_VALUE) & numReduceTasks;
    }
}
  • 自定義Partitioner的時候,注意同步修改numReduceTasks參數,與自定義的分區數保持一致。

排序

MapReduce工作流程排序機制

  • 不管程序邏輯是否需要,MapReduce框架的map階段和reduce階段都會進行排序,默認情況下按照字典序排序
  • 對MapTask, 它會將處理的結果暫時放到內存中的環形緩衝區,當環形緩衝區的使用率達到80%的閾值後,再對緩衝區的數據進行快速排序,並將排序後的數據溢寫到磁盤上,當MapTask所有數據處理完畢後,它會將磁盤上所有文件歸併,歸併過程使用是堆排序(優先隊列實現)
  • 對ReduceTask, 它從每個MapTask上遠程拷貝相應數據文件,如果文件大小超過一定閾值,則溢寫到磁盤,否則存儲在內存中。如果磁盤上文件數據達到或超過閾值,則進行一次歸併合併成一個更大的文件;如果內存中文件大小或者數目超過一定閾值,則進行一次合併後將數據溢寫到磁盤上。當所有數據拷貝完畢後,ReduceTask統一對內存和磁盤上的所有數據進行一次歸併. 和MapTask一樣,歸併過程使用的是堆排序

MapReduce排序應用:全排序

單分區

MapReduce框架自動實現輸出結果的全排序,缺點:效率低

分區排序

自定義Partitioner,按照需要排序的鍵分區,不同的鍵按照順序進入不同的分區

採樣分區

在數據分佈不均勻的情況下,按照等距間隔劃分Partitioner可能造成數據傾斜,拖慢整個任務的執行速度. 使用InputSampler對數據進行採樣,將得到的數據採樣分區間隔寫入文件,使用TotalOrderPartitioner類讀取分隔值作爲分區依據

MapReduce排序應用:group邏輯

在MapReduce裏實現類似SQL的groupby的邏輯,可以在自定義map輸出鍵類型的基礎上繼承GroupingComparator實現讓部分字段相同的鍵進入同一個reduce方法

Join in MapReduce

MapReduce實現SQL的join邏輯,有兩種方法,一種是Reduce側join,一種是Map側join

Reduce側join

在map裏給記錄增加一個標籤字段,通過獲取切片的路徑,獲取每條記錄來自哪一個輸入文件/表的信息,給不同輸入文件/表的輸入打不同標籤. map的輸出以連接的字段(join on)爲鍵,其餘輸入字段和新增的標籤字段爲值,連接字段相同、來源文件/表不同的記錄會在同一個partition中給同一個reduce處理,reduce中將它們連接起來。
reduce側join適合兩表或多表較大,無法放入節點內存的情形,開銷較大。

Map側join

把小表/文件分發到map節點上,在mapper的setup階段,將小表/文件讀入內存,在map中直接和大表join
map側join適合需要連接的表只有一張表很大,其餘表都很小,可以全部放入節點內存的情形,開銷小。

調優

輸入階段

輸入大量小文件,造成切片過小,map實際運行時間很短。調優方法:

  • CombineTextInputFormat合併大量小文件
  • 開啓JVM重用:一個map運行在一個JVM上,開啓JVM重用,該map運行完畢後,JVM繼續運行其他map. 通過設置mapreduce.job.jvm.numtasks值實現

map階段

  • 減少溢寫次數:調整io.sort.mb(默認100M)以及sort.spill.percent(默認80%)增大環形緩衝區以及環形緩衝區發生溢寫的閾值
  • 減少merge次數:調整io.sort.factor參數,增大merge的文件數
  • 業務邏輯允許的前提下,使用Combiner減少shuffle階段的IO

reduce階段

  • 合理設置reduce並行度
  • 設置map/reduce共存:調整slowstart.completedmaps,使map運行到一定程度後reduce就開始運行
  • 設置mapred.job.reduce.input.buffer.percent(默認爲0.0),將一定比例的shuffle後的buffer不落盤,直接給reduce任務

IO傳輸

  • 使用Snappy/LZO壓縮方式傳輸map輸出
  • 使用SequenceFile二進制文件在多個MapReduce任務之間傳輸

數據傾斜

減小數據傾斜的方法:

  • 抽樣和範圍分區:對原始數據集抽樣預設分區邊界
  • 自定義分區
  • 使用Combiner
  • 使用map join替代reduce join
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章