MapReduce 內部實現機制,你真的懂嗎?

微信公衆號:小林玩大數據
作者:林中鳥
如果你覺得此文對你有幫助,歡迎點贊!

1. MapReduce 簡介

2. MapReduce 的處理特點

3. MapReduce 內部實現機制

   3.1 MapReduce 執行流程

   3.2 Map階段

   3.3 Reduce 階段

1. MapReduce 簡介


MapReduce 編程範式將數據處理拆分成了兩個基本階段:Map 階段與 Reduce 階段。每個階段的輸入和輸出均爲鍵值對。

 

Map 階段對應的進程爲Mapper。Mapper 是在 JVM 上運行的 Java 進程,通常在要處理的數據節點上啓動。利用數據本地性是 MapReduce 的一個很重要的原則。對於大數據集而言,將處理的進程分配至包含數據的節點上(計算向數據移動),比通過網絡傳輸數據要高效的多。當 Mapper 處理完輸入數據之後,會進入 sort & shuffle 階段,在此階段,數據會進行排序與分區,產生 (k,v,p) 型數據。

Recucer 階段會將分區後的有序數據通過網絡 I/O 發送給 Reducer 所在的 JVM ,Reducer 會讀取這些按照鍵進行分區的有序數據。Reducer 拿到這些記錄,reducer()方法可以對數據執行各種操作。Reducer 通常會把處理好的數據聚合到 HDFS 或 HBase 的存儲中。
總的來說,JVM 有兩類進程,一類讀取無序數據,一類處理分區後的有序數據。

2. MapReduce 的處理特點


  • Mapper 以鍵值對形式處理輸入數據,並且每次只能處理一個鍵值對。Mapper 的個數是由框架而非開發人員決定的。
  • Mapper 將鍵值對作爲輸出傳遞給 Reducer ,但是不能將數據傳遞給其它 Mapper。Reducer 之間也不能通信。
  • Mapper 和 Reducer 通常不會使用太多的內存,一般會將 JVM 的堆大小設置爲相對較小的值。
  • 一般來說,每一個 Reducer 都會有單個輸出的數據流,在默認情況下,這是一個文件合集,命名形式如:Part-r-0000、Part-r-00001等。存在客戶端指定的 HDFS 目錄下。
  • Mapper 和 Reducer 的輸出均會寫入磁盤。如果 Reducer 輸出的結果還需要額外的處理,那麼整個數據集將會寫入磁盤中,再讀取一遍。這種模式被成爲同步屏障(synchronization barrier)。這也是使用 MapReduce 迭代式處理數據比較低效的主要原因。

注意:MapReduce 有兩大缺點:其一是啓動時間較長。即使 MapReduce 過程幾乎什麼都不做,啓動也需要大概10~30秒;其二是 MapReduce 會頻繁寫入磁盤,以便容錯。這兩大弱點使 MapReduce 不適用於迭代算法。

3. MapReduce 內部實現機制


3.1 MapReduce 執行流程

  1. 大數據集文件在集羣的 HDFS 中以塊存儲,並把業務邏輯的計算程序上傳到 HDFS 中,客戶端提交作業給ResourceManager,ResourceManager 會在集羣內隨機挑一個不忙的節點,隨機創建一個AppMstr進程。

  2. AppMstr 會從 HDFS 中獲取由 InputFormat 實現好的邏輯切片清單信息,並向 ResourceManager 申請計算資源,ResourceManager 會根據切片清單信息返回一個 container 並開始作業。

  3. Map 計算過程中,一個 InputSplit 對應一個 Mapper,Map 計算結束後,通過 Hash 算法,計算 key 的hashCode值,再對分區數(partition)取模,實現 (k,v,p) 型數據。(相同的 key 爲一組,最後會被分配到同一個 Reducer)。

  4. 此時 不會直接寫入磁盤,否則 I/O 切換次數過於頻繁,內部採取了一個 buffer 機制,默認大小爲100M;當緩存滿了 80%(此參數可調)之後,讀取一次 I/O ,把緩存區中的數據根據 p(分區) 溢寫到磁盤,磁盤上的每個文件再根據 key 進行 sort,最終的結果是形成一個個內部由序,外部無序的小文件。

  5. 當 Map 輸出結束,對磁盤上的小文件進行一次歸併排序,歸併的過程將同一分區的數據放在一起,此時仍然內部的 key 有序;各個 Reduce 會從多個 Map shuffle 屬於自己分區的小文件,shuffle 到 reduce 中再一次歸併多個全排序文件進行 reduce 計算,形成以 Part-r-00000、Part-r-00001命名的文件,並寫到 HDFS 中。

注意:
1.上述第5步的再一次歸併成多個全排序文件,主要是爲了減少 I/O 時間。
2.上述過程的時間並非線性,在溢寫出採用了時間複用。舉個例子:假設前面有500個 Map ,有20個 Map 跑完了,剩下480個繼續跑,reduce 會把跑完的20個 Map 先 shuffle 來做歸併並計算。
3.雖然上述過程有一次 sort ,兩次歸併。 但是 reduce 沒有重新排序的能力,而是強依賴之前 Map 的 sort 結果。

MapReduce 執行流程圖

                                                                              MapReduce 執行流程圖

 

MapReduce 的強大之處在於,它不僅僅是由 Map 和 Reduce 任務構成的,其中還包括協同工作的多個組件。每個組件均可以由開發人員擴展。下面從 Map 階段、Reduce 階段兩個方面介紹一個任務執行所涉及的主要組件。

3.2 Map階段

Map 階段主要涉及的組件有:InputFormat、RecordReader、Mapper 的 setuP()、Mapper 的 map()、patitioner(分區器)、Mapper 的cleanup()、Combiner。

  1. InputFormat Map 的輸入數據主要是數據的邏輯分片,此過程主要由 InputFormat 類實現。該類主要有一下兩個抽象方法:
  • getSplits()
    這個方法實現的是將輸入分步到多個 Map 進程的具體邏輯。框架默認的是 TextInputFormat ,它會爲每個數據塊生成一個輸入分片,並將對應數據塊的位置發送給 Map 任務。框架會針對每個分片啓動一個 Mapper 進程執行處理。正因如此,開發人員常常假定 MapReduce 任務中,Mapper 的個數就是待處理數據集的數據塊個數。

createRecordReader()

  • 這一方法爲 Map 任務提供了 Reader 機制,賦予了 Map 訪問待處理數據的能力,該方法會返回一個記錄讀取器,一般常用的是 LineRecordReader 行記錄讀取器,
//獲取 InputSplit 抽象方法
public abstract List<InputSplit> getSplits(JobContext context) 
                                    throws IOException,InterruptedException;
//獲取記錄讀取器抽象方法
public abstract RecordReader<K,V> createRecordReader(InputSplit split,
               TaskAttemptContext context) throws IOException, InterruptedException;
  1. RecordReader RecordReader 類讀取數據塊的內容,將讀取到的鍵值對記錄返回到 Map 任務。初始化 RecordReader 實例,使用的是需要讀取的數據塊在文件中的起始位置,以及該文件在 HDFS 中的 URI 地址。尋址到這一起始位置之後,每次調用 nextKeyValue()方法查找下一個行分隔符,並讀取下一條記錄。

  2. Mapper 的 setuP() 在 Map 任務的 map 方法調用之前,Mapper 的 setup()方法會被首先調用一次。這一方法可以使開發人員初始化 Map 進程中會用到的變量和文件句柄。setup()最常用的作用是從配置對象中獲取配置項的值。

  3. Mapper 的 map() Mapper的核心方法就是 map()方法,開發人員需要重寫 map()方法。該方法有三個輸入參數:鍵(key)、值(value)及上下文(context)。其中 key 和 value 可以通過 RecordReader 獲得,它們包含map()方法需要處理的數據。context 對象爲 Mapper 更多行爲的實現提供了支持:將輸出發送給 Reduceer,從Configuration 對象中讀取值,計數器自增以彙報 Map 任務進度。

  4. Partitioner 分區器實現了在 Reducer 之間進行數據分區的邏輯。默認的 Partitioner 處理邏輯:首先獲得 key 的標準哈希函數散列,再與 Reducer(開發人員可控制數量) 數取模,餘數則決定這條記錄的目標 Reducer。

  5. Combiner MapReduce 中的Combiner可以提供一種簡單的方法,減少 Mapper 和 Reducer 間的數據傳輸。在 buffer 中,數據 sort(默認快排)完之後,如果定義了 Combiner,你就可以調用 combine()方法,對 Mapper 產生的數據進行聚合操作了,因爲這個方法會在 Mapper 執行的節點上執行,所以這種聚合行爲可以減少網絡 I/O。

  6. Mapper 的 cleanup() cleanup()方法在 map 方法處理了所有的數據之後調用。這裏通常要執行文件的關閉操作,以及最後的報告和總結,比如將最終狀態寫入日誌中。

3.3 Reduce 階段

Reducer 任務沒有 Map 任務那麼複雜,主要涉及的組件有:Shuffle、Reducer 的 setup()、Reducer 的reduce()、OutputFormat、Reducer 的 cleanup()。

  1. Shuffle 在 Reducer 開始之前,Reducer 任務會將 Mapper 的輸出從 Map 節點複製到 Reducer 節點中,這個過程稱爲 Shuffle。每一個 Reducer 都需要從多個 Mapper Shuffle 數據,所以我們要讓每一個 Reducer 按照 Map 任務的方式讀取本地的數據,提高處理性能。

  2. Reducer 的 setup() Reducer 的 setup()步驟與 Map 的 setup()相似。這一方法在 Reducer 處理單個記錄之前,通常用來初始化變量和文件句柄。

  3. Reducer 的 reduce() reduce()方法是進行絕大多數數據處理的地方。與 Map 的 map()方法類似:
    但在輸入方面有幾點不同,首先,key 是有序的;其次,value 從原來的一個 value 變成了 values(從一次處理一個值,變爲一次處理多個值)。
    輸出方面,map()方法中,調用 context.write(k,v)方法可以將輸出放入緩存區,緩存區中的數據會被 sort,然後被 Reducer 讀取。而在 reduce()方法中,調用 context.write(k,v)方法會將輸出發送給 OutputFormat。

  4. OutputFormat 在 Map 階段,InputFormat 負責處理輸入數據的讀取,OutputFormat 負責數據的格式化和把輸出數據寫出(通常是寫到 HDFS)。對於 OutputFormat 類來說,一個 Reducer 只會寫一個文件,因此在 HDFS 上看到一個 Reducer 對應一個 輸出文件。

林中鳥兒們,這期就和大家分享到這兒!掃一掃來個關注,下期爲大家解析 MapReduce 關鍵組件的源碼!敬請期待!

林中鳥公衆號

                林中鳥公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章