MapReduce知識點整理
基於版本:Hadoop 2.7.2
序列化
Q: 爲什麼Hadoop不使用Java自帶的序列化?
A: Java自帶的序列化框架過於重量級(附帶很多額外信息:校驗信息、Header、繼承體系等),網絡傳輸效率低,所以Hadoop自己實現了序列化機制(Writable
接口).
自定義可序列化類
- 實現
Writable
接口 - 反序列化用到反射,需要調用無參數構造方法,因此自定義的可序列化類需要有無參數構造方法
- 重新
write
方法(序列化)和readFields
方法(反序列化),注意序列化和反序列化時字段順序需要保持一致 - Hadoop的shuffle過程會對鍵排序,因此如果需要自定義的可序列化類對象作爲鍵,還需要實現
WritableComparable
接口 - 如果對象需要作爲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工作機制
- Read階段
- Map階段
- Collect階段
- 把計算好的
<k, v>
對以及元信息放在環形緩衝區,所謂環形緩衝區實際只是一個字節數組,用equator
標明瞭環的起始位置 <k, v>
對從equator
按照索引遞增方向存儲- 元信息(
partition
,keystart
,valstart
,vallen
,kvindex
)從equator
按照索引遞減方向存儲
- 把計算好的
- 溢寫階段:當環形緩衝區的內容佔緩衝區大小80%時,會把環形緩衝區的內容快速排序後寫入磁盤臨時文件(如果有combiner,則寫入磁盤前會先經過combiner),當MapTask的所有數據處理完成後,對臨時文件中的內容進行歸併,排序後的內容寫入磁盤(如果有combiner,則寫入磁盤前會再次經過combiner)
- Q: 爲什麼不等環形緩衝區滿再溢寫? A: 會出現寫入環形緩衝區阻塞
- Q: 爲什麼把
<k, v>
對與元信息放在同一個buffer? A: 提高buffer利用率,減少溢寫次數
關於MapTask的源碼級別的分析可以參考這篇博客:MapReduce源碼解析–Map解析
ReduceTask工作機制
- Copy階段
- Sort階段
- 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