1. MapTask 工作機制
整個Map階段流程大體如上圖所示。
簡單概述:inputFile通過split被邏輯切分為多個split文件,通過Record按行讀取內容給 map(用戶自己實現的)進行處理,數據被map處理結束之后交給OutputCollector收集器,對 其結果key進行分區(默認使用hash分區),然后寫入buwer,每個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 中, 會先對其進行分區處理,默認使用 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 框架中最關鍵的一個流程,這 個流程就叫 shuwle shuwle: 洗牌、發牌 ——(核心機制:數據分區,排序,分組,規約,合并 等過程)
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 的數據的最終整體有效性即可。 Shuwle 中的緩沖區大小會影響到 mapreduce 程序的執行效率,原則上說,緩沖區越大, 磁盤io的次數越少,執行速度就越快 緩沖區的大小可以通過參數調整, 參數:mapreduce.task.io.sort.mb 默認100M