MapReduce 詳解與原始碼分析

mcxiaoracle發表於2022-09-21

1 Split 階段

在 mapreduce 中的 map task 開始之前,將檔案按照指定的大小切割成若干個部分,每一部分稱為一個 split,預設是 split 的大小與 block 的大小相等,均為 128MB。split 大小由 minSize、maxSize、blocksize 決定,以 wordcount 程式碼為例,以下是 main()方法



進入 waitForCompletion(true)方法,進入 submit()方法:

進行提交:

找到  return submitter .submitJobInternal(Job.this, cluster); 進入,找到  int maps = writeSplits(job, submitJobDir);

找到寫分片機制,writeSplites,接著進入 進入 writeNewSplits() 方法;

進入 writeNewSplits()方法,可以看出該方法首先獲取 splits 陣列資訊後,排序,將會優先處理大檔案。最終返回 mapper 數量。

這其中又分為兩部分:確定切片數量 和 寫入切片資訊。確定切片數量的任務交由 FileInputFormat 的 getSplits(job)完成,寫入切片資訊的任務交由 JobSplitWriter.createSplitFiles(jobSubmitDir, conf, jobSubmitDir.getFileSystem(conf), array)方法,該方法會將切片資訊和 SplitMetaInfo 都寫入 HDFS 中。

return array.length; 返回的是 map 任務數,

預設 map 的數量是:  default_num = total_size / block_size; 實際的 mapper 數量就是輸入切片的數量,而切片的數量又由使用的輸入格式決定,預設為 TextInputFormat,該類為 FileInputFormat 的子類。

確定切片數量的任務交由 FileInputFormat 的 getSplits(job)完成。FileInputFormat 繼承自抽象類 InputFormat,該類定義了 MapReduce 作業的輸入規範,其中的抽象方法 List getSplits(JobContext context)定義瞭如何將輸入分割為 InputSplit,不同的輸入有不同的分隔邏輯,而分隔得到的每個 InputSplit 交由不同的 mapper 處理,因此該方法的返回值確定了 mapper 的數量。



2 Map 階段

每個 map task 都有一個記憶體緩衝區, map 的輸出結果先寫到記憶體中的環形緩衝區,緩衝區為 100M,不斷的向緩衝區力寫資料,當達到 80M 時,需要將緩衝區中的資料以一個臨時檔案的方式存到磁碟,當整個 map task 結束後再對磁碟中這個 map task 所產生的所有臨時檔案做合併,生成最終的輸出檔案。

最後,等待 reduce task 來拉取資料。當然,如果 map task 的結果不大,能夠完全儲存到記憶體緩衝區,且未達到記憶體緩衝區的閥值,那麼就不會有寫臨時檔案到磁碟的操作,也不會有後面的合併。在寫入的過程中會進行分割槽、排序、combine 操作。


環形緩衝區:是使用指標機制把記憶體中的地址首尾相接形成一個儲存中間資料的快取區域,預設 100MB;80M 閾值,20M 緩衝區,是為了解決寫入環形緩衝區資料的速度大於寫出到 spill 檔案的速度是資料的不丟失;Spill 檔案:spill 檔案是環形緩衝區到達閾值後寫入到磁碟的單個檔案.這些檔案在 map 階段計算結束時,會合成分好區的一個 merge 檔案供給給 reduce 任務抓取;spill 檔案過小的時候,就不會浪費 io 資源合併 merge;預設情況下 3 個以下 spill 檔案不合並;對於在環形緩衝區中的資料,最終達不到 80m 但是資料已經計算完畢的情況,map 任務將會呼叫 flush 將緩衝區中的資料強行寫出 spill 檔案。


按照 key 合併成大檔案,減少網路開銷



2.1 分割槽

假設有聽個 reduce 任務,則分割槽的計算如下:


2.2 排序

在對 map 結果進行分割槽之後,對於落在相同的分割槽中的鍵值對,要進行排序。

3 Shuffle 階段

Shuffle 過程是 MapReduce 的核心,描述著資料從 map task 輸出到 reduce task 輸入的這段過程。

reducetask 根據自己的分割槽號,去各個 maptask 分割槽機器上取相應的結果分割槽資料,reducetask 會將這些檔案再進行合併(歸併排序)。

所有相同 key 的資料彙集到一個 partition。

將相同的 key value 匯聚到一起, 但不計算

4 Reduce 階段

reduce 階段分三個步驟:

1 reduce 任務會建立並行的抓取執行緒(fetcher)負責從完成的 map 任務中獲取結果檔案,是否完成是透過 rpc 心跳監聽,透過 http 協議抓取;預設是 5 個抓取執行緒,可調,為了是整體並行,在 map 任務量大,分割槽多的時候,抓取執行緒調大;

2 抓取過來的資料會先儲存在記憶體中,如果記憶體過大也溢位,不可見,不可調,但是單位是每個 merge 檔案,不會切分資料;每個 merge 檔案都會被封裝成一個 segment 的物件,這個物件控制著這個 merge 檔案的讀取記錄操作,有兩種情況出現:在記憶體中有 merge 資料 •在溢寫之後存到磁碟上的資料 •透過建構函式的區分,來分別建立對應的 segment 物件



3 這種 segment 物件會放到一個記憶體佇列中 MergerQueue,對記憶體和磁碟上的資料分別進行合併,記憶體中的 merge 對應的 segment 直接合並,磁碟中的合併與一個叫做合併因子的 factor 有關(預設是 10)



排序問題,MergerQueue 繼承輪換排序的介面,每一個 segment 是排好序的,而且按照 key 的值大小邏輯(和真的大小沒關係);每一個 segment 的第一個 key 都是邏輯最小,而所有的 segment 的排序是按照第一個 key 大小排序的,最小的在前面,這種邏輯總能保證第一個 segment 的第一個 key 值是所有 key 的邏輯最小檔案合併之後,最終交給 reduce 函式計算的,是 MergeQueue 佇列,每次計算的提取資料邏輯都是提取第一個 segment 的第一個 key 和 value 資料,一旦 segment 被呼叫了提取 key 的方法,MergeQueue 佇列將會整體重新按照最小 key 對 segment 排序,最終形成整體有序的計算結果;



Reduce 任務數量 在大資料量的情況下,如果只設定 1 個 Reduce 任務,其他節點將被閒置,效率底下 所以將 Reduce 設定成一個較大的值(max:72).調節 Reduce 任務數量的方法 一個節點的 Reduce 任務數並不像 Map 任務數那樣受多個因素制約



參考博文:

https://gitchat.blog.csdn.net/article/details/104190423?spm=1001.2101.3001.6650.7&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7ERate-7-104190423-blog-81115646.pc_relevant_multi_platform_whitelistv4&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7ERate-7-104190423-blog-81115646.pc_relevant_multi_platform_whitelistv4&utm_relevant_index=8













來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69949806/viewspace-2915687/,如需轉載,請註明出處,否則將追究法律責任。

相關文章