1. MapTask 工作機制
簡單概述:inputFile通過split被邏輯切分爲多個split文件,通過Record按行讀取內容給map(用戶自己實現的)進行處理,數據被map處理結束之後交給OutputCollector收集器,對其結果key進行分區(默認使用hash分區),然後寫入buffer,每個map task都有一個內存緩衝區,存儲着map的輸出結果,當緩衝區快滿的時候需要將緩衝區的數據以一個臨時文件的方式存放到磁盤,當整個map task結束後再對磁盤中這個map task產生的所有臨時文件做合併,生成最終的正式輸出文件,然後等待reduce task來拉數據
詳細步驟
-
讀取數據組件 InputFormat (默認 TextInputFormat) 會通過
getSplits
方法對輸入目錄中文件進行邏輯切片規劃得到block
, 有多少個block
就對應啓動多少個MapTask
. -
將輸入文件切分爲
block
之後, 由RecordReader
對象 (默認是LineRecordReader) 進行讀取, 以\n
作爲分隔符, 讀取一行數據, 返回<key,value>
. Key 表示每行首字符偏移值, Value 表示這一行文本內容 -
讀取
block
返回<key,value>
, 進入用戶自己繼承的 Mapper 類中,執行用戶重寫的 map 函數, RecordReader 讀取一行這裏調用一次 -
Mapper 邏輯結束之後, 將 Mapper 的每條結果通過
context.write
進行collect數據收集. 在 collect 中, 會先對其進行分區處理,默認使用 HashPartitioner-
MapReduce 提供
Partitioner
接口, 它的作用就是根據Key
或Value
及Reducer
的數量來決定當前的這對輸出數據最終應該交由哪個Reduce task
處理, 默認對 Key Hash 後再以 Reducer 數量取模. 默認的取模方式只是爲了平均 Reducer 的處理能力, 如果用戶自己對 Partitioner 有需求, 可以訂製並設置到 Job 上
-
-
接下來, 會將數據寫入內存, 內存中這片區域叫做環形緩衝區, 緩衝區的作用是批量收集 Mapper 結果, 減少磁盤 IO 的影響. 我們的 Key/Value 對以及 Partition 的結果都會被寫入緩衝區. 當然, 寫入之前,Key 與 Value 值都會被序列化成字節數組
-
環形緩衝區其實是一個數組, 數組中存放着 Key, Value 的序列化數據和 Key, Value 的元數據信息, 包括 Partition, Key 的起始位置, Value 的起始位置以及 Value 的長度. 環形結構是一個抽象概念
-
緩衝區是有大小限制, 默認是 100MB. 當 Mapper 的輸出結果很多時, 就可能會撐爆內存, 所以需要在一定條件下將緩衝區中的數據臨時寫入磁盤, 然後重新利用這塊緩衝區. 這個從內存往磁盤寫數據的過程被稱爲 Spill, 中文可譯爲溢寫. 這個溢寫是由單獨線程來完成, 不影響往緩衝區寫 Mapper 結果的線程. 溢寫線程啓動時不應該阻止 Mapper 的結果輸出, 所以整個緩衝區有個溢寫的比例
spill.percent
. 這個比例默認是 0.8, 也就是當緩衝區的數據已經達到閾值buffer size * spill percent = 100MB * 0.8 = 80MB
, 溢寫線程啓動, 鎖定這 80MB 的內存, 執行溢寫過程. Mapper 的輸出結果還可以往剩下的 20MB 內存中寫, 互不影響
-
-
當溢寫線程啓動後, 需要對這 80MB 空間內的 Key 做排序 (Sort). 排序是 MapReduce 模型默認的行爲, 這裏的排序也是對序列化的字節做的排序
-
如果 Job 設置過 Combiner, 那麼現在就是使用 Combiner 的時候了. 將有相同 Key 的 Key/Value 對的 Value 加起來, 減少溢寫到磁盤的數據量. Combiner 會優化 MapReduce 的中間結果, 所以它在整個模型中會多次使用
-
那哪些場景才能使用 Combiner 呢? 從這裏分析, Combiner 的輸出是 Reducer 的輸入, Combiner 絕不能改變最終的計算結果. Combiner 只應該用於那種 Reduce 的輸入 Key/Value 與輸出 Key/Value 類型完全一致, 且不影響最終結果的場景. 比如累加, 最大值等. Combiner 的使用一定得慎重, 如果用好, 它對 Job 執行效率有幫助, 反之會影響 Reducer 的最終結果
-
-
合併溢寫文件, 每次溢寫會在磁盤上生成一個臨時文件 (寫之前判斷是否有 Combiner), 如果 Mapper 的輸出結果真的很大, 有多次這樣的溢寫發生, 磁盤上相應的就會有多個臨時文件存在. 當整個數據處理結束之後開始對磁盤中的臨時文件進行 Merge 合併, 因爲最終的文件只有一個, 寫入磁盤, 並且爲這個文件提供了一個索引文件, 以記錄每個reduce對應數據的偏移量
配置
配置 | 默認值 | 解釋 |
---|---|---|
mapreduce.task.io.sort.mb |
100 | 設置環型緩衝區的內存值大小 |
mapreduce.map.sort.spill.percent |
0.8 | 設置溢寫的比例 |
mapreduce.cluster.local.dir |
${hadoop.tmp.dir}/mapred/local |
溢寫數據目錄 |
mapreduce.task.io.sort.factor |
10 | 設置一次合併多少個溢寫文件 |
2. ReduceTask 工作機制
Reduce 大致分爲 copy、sort、reduce 三個階段,重點在前兩個階段。copy 階段包含一個 eventFetcher 來獲取已完成的 map 列表,由 Fetcher 線程去 copy 數據,在此過程中會啓動兩個 merge 線程,分別爲 inMemoryMerger 和 onDiskMerger,分別將內存中的數據 merge 到磁盤和將磁盤中的數據進行 merge。待數據 copy 完成之後,copy 階段就完成了,開始進行 sort 階段,sort 階段主要是執行 finalMerge 操作,純粹的 sort 階段,完成之後就是 reduce 階段,調用用戶定義的 reduce 函數進行處理
詳細步驟
-
Copy階段
,簡單地拉取數據。Reduce進程啓動一些數據copy線程(Fetcher),通過HTTP方式請求maptask獲取屬於自己的文件。 -
Merge階段
。這裏的merge如map端的merge動作,只是數組中存放的是不同map端copy來的數值。Copy過來的數據會先放入內存緩衝區中,這裏的緩衝區大小要比map端的更爲靈活。merge有三種形式:內存到內存;內存到磁盤;磁盤到磁盤。默認情況下第一種形式不啓用。當內存中的數據量到達一定閾值,就啓動內存到磁盤的merge。與map 端類似,這也是溢寫的過程,這個過程中如果你設置有Combiner,也是會啓用的,然後在磁盤中生成了衆多的溢寫文件。第二種merge方式一直在運行,直到沒有map端的數據時才結束,然後啓動第三種磁盤到磁盤的merge方式生成最終的文件。 -
合併排序
。把分散的數據合併成一個大的數據後,還會再對合並後的數據排序。 -
對排序後的鍵值對調用reduce方法
,鍵相等的鍵值對調用一次reduce方法,每次調用會產生零個或者多個鍵值對,最後把這些輸出的鍵值對寫入到HDFS文件中。
3. Shuffle 過程
map 階段處理的數據如何傳遞給 reduce 階段,是 MapReduce 框架中最關鍵的一個流程,這個流程就叫 shuffleshuffle: 洗牌、發牌 ——(核心機制:數據分區,排序,分組,規約,合併等過程)
Picture4.png
shuffle 是 Mapreduce 的核心,它分佈在 Mapreduce 的 map 階段和 reduce 階段。一般把從 Map 產生輸出開始到 Reduce 取得數據作爲輸入之前的過程稱作 shuffle。
-
Collect階段
:將 MapTask 的結果輸出到默認大小爲 100M 的環形緩衝區,保存的是 key/value,Partition 分區信息等。 -
Spill階段
:當內存中的數據量達到一定的閥值的時候,就會將數據寫入本地磁盤,在將數據寫入磁盤之前需要對數據進行一次排序的操作,如果配置了 combiner,還會將有相同分區號和 key 的數據進行排序。 -
Merge階段
:把所有溢出的臨時文件進行一次合併操作,以確保一個 MapTask 最終只產生一箇中間數據文件。 -
Copy階段
:ReduceTask 啓動 Fetcher 線程到已經完成 MapTask 的節點上覆制一份屬於自己的數據,這些數據默認會保存在內存的緩衝區中,當內存的緩衝區達到一定的閥值的時候,就會將數據寫到磁盤之上。 -
Merge階段
:在 ReduceTask 遠程複製數據的同時,會在後臺開啓兩個線程對內存到本地的數據文件進行合併操作。 -
Sort階段
:在對數據進行合併的同時,會進行排序操作,由於 MapTask 階段已經對數據進行了局部的排序,ReduceTask 只需保證 Copy 的數據的最終整體有效性即可。Shuffle 中的緩衝區大小會影響到 mapreduce 程序的執行效率,原則上說,緩衝區越大,磁盤io的次數越少,執行速度就越快緩衝區的大小可以通過參數調整, 參數:mapreduce.task.io.sort.mb 默認100M