MapReduce過程詳解

Hadoop越來越火,圍繞Hadoop的子項目更是增長迅速,光Apache官網上列出來的就十幾個,但是萬變不離其宗,大部分項目都是基於Hadoop Common。

MapReduce更是核心中的核心。那麼到底什麼是MapReduce,它具體是怎麼工作的呢?

關於它的原理,說簡單也簡單,隨便畫個圖噴一下Map和Reduce兩個階段似乎就完了。但其實這裏面還包含了很多的子階段,尤其是Shuffle,很多資料裏都把它稱爲MapReduce的“心臟”,和所謂“奇蹟發生的地方”。真正能說清楚其中關係的人就沒那麼多了。可是瞭解這些流程對我們理解和掌握MapReduce並對其進行調優是非常有用的。

首先我們看一幅圖,包含了從頭到尾的整個過程,後面對所有步驟的解釋都以此圖作爲參考(此圖100%原創)


這張圖簡單來說,就是說在我們常見的Map和Reduce之間還有一系列的過程,其中包括partition、Sort、Combine、copy、merge等,而這些過程往往被統稱爲“Shuffle”也就是“混洗”,而Shuffle的目的就是對數據進行梳理,排序,以便科學的方式分發給每個Reducer,以便高效的進行計算和處理(難怪人家說這事奇蹟發生的地方,原理這裏面有這麼多花花,能沒奇蹟嘛?)

如果您是Hadoop的大牛,看了這幅圖可能馬上就要跳出來了,不對!還有一個spill過程云云。。。

且慢,關於spill,我認爲只是一個實現細節,其實就是MapReduce利用內存緩衝的方式提高效率,整個的過程和原理並沒有受影響,所以在此忽略掉spill過程,以便更好了解。

光看原理圖還是有點費解是吧?沒錯!我一直認爲,沒有例子的文章就是耍流氓,所以我們就用大家耳熟能詳的WordCount作爲例子,開始我們的討論。

先創建兩個文本文件,作爲我們例子的輸入:

file1 內容爲:

[html] view plaincopy
  1. My name is Tony  
  2. My company is pivotal  
file2 內容爲:

[html] view plaincopy
  1. My name is Lisa  
  2. My company is EMC  

第一步:對輸入分片進行map()處理

首先我們的輸入就是兩個文件,默認情況下是兩個split,對應前面圖中的split0,split1。兩個split默認會分給兩個Mapper來處理,WordCount例子相當地暴力,這一步裏面就是把文件內容分解爲單詞和1,其中的單詞就是我們的key,後面的數字就是對應的值,也就是value【在此假設各位都對WordCount程序爛熟於心】。

那麼對應兩個Mapper的輸出就是:

split0被處理後的數據爲:

[html] view plaincopy
  1. My       1  
  2. name    1  
  3. is         1  
  4. Tony     1  
  5. My          1  
  6. company     1  
  7. is       1  
  8. Pivotal   1  
split1被處理後的數據爲:

[html] view plaincopy
  1. My       1  
  2. name    1  
  3. is       1  
  4. Lisa     1  
  5. My       1  
  6. company  1  
  7. is       1  
  8. EMC     1  
第二步:對map()的輸出結果進行Partition

partition是什麼?partition就是分區。

爲什麼要分區?因爲有時候會有多個Reducer,partition就是提前對輸入進行處理,根據將來的Reducer進行分區,到時候Reducer處理的時候,只需要處理分給自己的數據就可以了。

如何分區?主要的分區方法就是按照key不同,把數據分開,其中很重要的一點就是要保證key的唯一性,因爲將來做Reduce的時候很可能是在不同的節點上做的,如果一個key同時存在兩個節點上,Reduce的結果就會出問題,所以很常見的partition方法就是哈希。

結合我們的例子,我們這裏假設有兩個Reducer,前面兩個Split做完Partition的結果就會如下:

split0的數據經過map()後再進行分區

partition 1:

[html] view plaincopy
  1. company 1  
  2. is      1  
  3. is    1  
Partition 2:

[html] view plaincopy
  1. My     1  
  2. My    1  
  3. name  1  
  4. Pivotal   1  
  5. Tony    1  
注:按Key進行hash並且對reducer數量(這裏設置爲2)取模,所以結果只能是兩個。


split1的數據經過map()後再進行分區(同split0):

partition 1:

[html] view plaincopy
  1. company 1  
  2. is    1  
  3. is      1  
  4. EMC   1  

Partition 2:

[html] view plaincopy
  1. My     1  
  2. My       1  
  3. name   1  
  4. Lisa     1  
注:其中partition1是給Reducer1處理的,partition2是給Reducer2處理的。這裏我們可以看到,partition只是把所有的條目按照key分了一個區,沒有其他任何處理,每個區裏面的key都不會出現在另外一個區裏面。


第三步:sort

sort就是排序咯,其實這個過程在我看來並不是必須的,完全可以交給客戶端自己的程序來處理。那爲什麼還要排序呢?可能是寫MapReduce的大牛們想,“大部分reduce程序應該都希望輸入的是已經按key排序號的數據,如果是這樣,那麼我們就乾脆順手幫你做掉啦!”

那麼我們假設對前面的數據再進行排序,結果如下:

split0 的partition 1中的數據排序後如下:

[html] view plaincopy
  1. company 1  
  2. is      1  
  3. is    1  

split0的partition 2中的數據排序後如下:

[html] view plaincopy
  1. My     1  
  2. My    1  
  3. name  1  
  4. Pivotal   1  
  5. Tony    1  

split1的partition 1中的數據排序後如下:

[html] view plaincopy
  1. company 1  
  2. EMC   1  
  3. is    1  
  4. is      1  

split1的partition 2中的數據排序後如下:

[html] view plaincopy
  1. Lisa   1  
  2. My     1  
  3. My       1  
  4. name   1  
注:這裏可以看到,每個partition裏面的條目都按照key的順序做了排序。


第四步:Combine

什麼是Combine呢?combine其實可以理解爲一個mini Reduce過程,它發生在前面Map的輸出結果之後,目的就是在結果送到Reducer之前先對其進行一次計算,以減少文件的大小,方便後面的傳輸。但這一步不是必須的。

按照前面的輸出,執行Combine:

split0的partition 1的數據爲:

[html] view plaincopy
  1. company 1  
  2. is      2  
split0的partition 2的數據爲:

[html] view plaincopy
  1. My     2  
  2. name  1  
  3. Pivotal   1  
  4. Tony    1  

split1的partition1 的數據爲:

[html] view plaincopy
  1. Partition 1:  
  2. company 1  
  3. EMC   1  
  4. is    2  

split1的partition 2的數據爲:

[html] view plaincopy
  1. Lisa   1  
  2. My     2  
  3. name   1  
注:針對前面的輸出結果,我們已經局部地統計了is和My的出現頻率,減少了輸出文件的大小。


第五步:copy

下面就要準備把輸出結果傳送給Reducer了。這個階段被稱爲Copy,但事實上我認爲叫它Download更爲合適,因爲實現的時候,是通過http的方式,由Reducer節點向各個Mapper節點下載屬於自己分區的數據。

那麼根據前面的partition,下載完的結果如下:

Reducer 節點1的共包含兩個文件(split0的partition1和split1的partition1):

[html] view plaincopy
  1. Partition 1:  
  2. company 1  
  3. is      2  
  4.    
  5. Partition 1:  
  6. company  1  
  7. EMC    1  
  8. is    2  

Reducer 節點2也是兩個文件(split0的partition1和split1的partition2):

[html] view plaincopy
  1.  Partition 2:   
  2. My     2  
  3. name  1  
  4. Pivotal   1  
  5. Tony    1  
  6.    
  7. Partition 2:  
  8. Lisa   1  
  9. My     2  
  10. name   1  
注:通過Copy,相同Partition的數據落到了同一個節點上。



第六步:Merge

如上一步所示,此時Reducer得到的文件是從不同的Mapper那裏下載到的,需要對他們進行合併爲一個文件,所以下面這一步就是Merge,結果如下:

Reducer 節點1的數據如下:

[html] view plaincopy
  1. company 1  
  2. company  1  
  3. EMC    1  
  4. is      2  
  5. is    2   
Reducer節點2的數據如下:

[html] view plaincopy
  1. Lisa  1  
  2. My     2  
  3. My    2  
  4. name  1  
  5. name  1  
  6. Pivotal   1  
  7. Tony    1  
注:Map端也有merge的過程,發生在環形緩衝區部分。


第七步:reduce處理

終於可以進行最後的Reduce啦,這步相當簡單咯,根據每個文件中的內容最後做一次統計,結果如下:

Reducer節點1的數據:

[html] view plaincopy
  1. company 2  
  2. EMC    1  
  3. is      4  
Reducer節點2的數據:

[html] view plaincopy
  1. Lisa  1  
  2. My     4  
  3. name  2  
  4. Pivotal   1  
  5. Tony    1  
至此大功告成!我們成功統計出兩個文件裏面每個單詞的數目,同時把他們存入到兩個輸出文件中,這兩個輸出文件也就是傳說中的part-r-00000和part-r-00001,看看兩個文件的內容,再回頭想想最開始的Partition,應該是清楚了其中的奧祕吧。

如果你在你自己的環境中運行的WordCount只有part-r-00000一個文件的話,那應該是因爲你使用的是默認設置,默認一個Job只有一個Reducer。

如果你想設置兩個,你可以:

1.在源代碼中加入 job.setNumReduceTasks(2),設置這個Job的Reducer爲兩個。

或者

2.mapred-site.xml中設置下面參數並重啓服務

[html] view plaincopy
  1. <property>  
  2.   <name>mapred.reduce.tasks</name>  
  3.   <value>2</value>  
  4. </property>  
如果在配置文件中設置,整個集羣都會默認使用兩個Reducer了。

結束語:

本文大致描述了一下MapReduce的整個過程以及每個階段所做的事情,並沒有涉及具體的Job,resource的管理和控制,因爲那個是第一代MapReduce框架和Yarn框架的主要區別。而兩代框架中上述MapReduce的原理是差不多的。


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