Hadoop之 MapReducer工作過程

張衝andy發表於2018-02-05
1. 從輸入到輸出

一個MapReducer作業經過了inputmapcombinereduceoutput五個階段,其中combine階段並不一定發生,map輸出的中間結果被分到reduce的過程成為shuffle(資料清洗)。

shuffle階段還會發生copy(複製)和sort(排序)。

在MapReduce的過程中,一個作業被分成Map和Reducer兩個計算階段,它們由一個或者多個Map任務和Reduce任務組成。如下圖所示,一個MapReduce作業從資料的流向可以分為Map任務和Reduce任務。當使用者向Hadoop提交一個MapReduce作業時,JobTracker則會根據各個TaskTracker週期性傳送過來的心跳資訊綜合考慮TaskTracker的資源剩餘量,作業優先順序,作業提交時間等因素,為TaskTracker分配合適的任務。Reduce任務預設會在Map任務數量完成5%後才開始啟動。

 

Map任務的執行過程可以概括為:首先通過使用者指定的InputFormat類中的getSplits方法和next方法將輸入檔案切片並解析成鍵值對作為map函式的輸入。然後map函式經過處理之後將中間結果交給指定的Partitioner處理,確保中間結果分發到指定的Reduce任務處理,此時如果使用者指定了Combiner,將執行combine操作。最後map函式將中間結果儲存到本地。

Reduce任務的執行過程可以概括為:首先需要將已經完成Map任務的中間結果複製到Reduce任務所在的節點,待資料複製完成後,再以key進行排序,通過排序,將所有key相同的資料交給reduce函式處理,處理完成後,結果直接輸出到HDFS上。

2. input

如果使用HDFS上的檔案作為MapReduce的輸入,MapReduce計算框架首先會用org.apache.hadoop.mapreduce.InputFomat類的子類FileInputFormat類將作為輸入HDFS上的檔案切分形成輸入分片(InputSplit),每個InputSplit將作為一個Map任務的輸入,再將InputSplit解析為鍵值對。InputSplit的大小和數量對於MaoReduce作業的效能有非常大的影響。

InputSplit只是邏輯上對輸入資料進行分片,並不會將檔案在磁碟上分成分片進行儲存。InputSplit只是記錄了分片的後設資料節點資訊,例如起始位置,長度以及所在的節點列表等。資料切分的演算法需要確定InputSplit的個數,對於HDFS上的檔案,FileInputFormat類使用computeSplitSize方法計算出InputSplit的大小,程式碼如下:

  1. protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
  2.    return Math.max(minSize, Math.min(maxSize, blockSize));
  3. }

其中 minSize 由mapred-site.xml檔案中的配置項mapred.min.split.size決定,預設為1;maxSize 由mapred-site.xml檔案中的配置項mapred.max.split.size決定,預設為9223 372 036 854 775 807;而blockSize是由hdfs-site.xml檔案中的配置項dfs.block.size決定,預設為67 108 864位元組(64M)。所以InputSplit的大小確定公式為:

  1. max(mapred.min.split.size, min(mapred.max.split.size, dfs.block.size));

一般來說,dfs.block.size的大小是確定不變的,所以得到目標InputSplit大小,只需改變mapred.min.split.size 和 mapred.max.split.size 的大小即可。InputSplit的數量為檔案大小除以InputSplitSize。InputSplit的原資料資訊會通過一下程式碼取得:

  1. splits.add(new FileSplit(path, length - bytesRemaining, splitSize, blkLocations[blkIndex].getHosts()));

從上面的程式碼可以發現,後設資料的資訊由四部分組成:檔案路徑,檔案開始位置,檔案結束位置,資料塊所在的host。

對於Map任務來說,處理的單位為一個InputSplit。而InputSplit是一個邏輯概念,InputSplit所包含的資料是仍然儲存在HDFS的塊裡面,它們之間的關係如下圖所示:

當輸入檔案切分為InputSplit後,由FileInputFormat的子類(如TextInputFormat)的createRecordReader方法將InputSplit解析為鍵值對,程式碼如下:

  1.  public RecordReader<LongWritable, Text>
  2.    createRecordReader(InputSplit split,
  3.                       TaskAttemptContext context) {
  4.    String delimiter = context.getConfiguration().get(
  5.        "textinputformat.record.delimiter");
  6.    byte[] recordDelimiterBytes = null;
  7.    if (null != delimiter)
  8.      recordDelimiterBytes = delimiter.getBytes(Charsets.UTF_8);
  9.    return new LineRecordReader(recordDelimiterBytes);
  10.  }

此處預設是將行號作為鍵。解析出來的鍵值對將被用來作為map函式的輸入。至此input階段結束。

3. map及中間結果的輸出

InputSplit將解析好的鍵值對交給使用者編寫的map函式處理,處理後的中間結果會寫到本地磁碟上,在刷寫磁碟的過程中,還做了partition(分割槽)和 sort(排序)的操作。

map函式產生輸出時,並不是簡單的刷寫磁碟。為了保證I/O效率,採取了先寫到記憶體的環形記憶體緩衝區,並做一次預排序,如下圖所示:

每個Map任務都有一個環形記憶體緩衝區,用於儲存map函式的輸出。預設情況下,緩衝區大小是100M,該值可以通過mapred-site.xml檔案中的io.sort.mb的配置項配置。一旦緩衝區內容達到閾值(由mapred-site.xml檔案的io.sort.spill.percent的值決定,預設為0.80 或者 80%),一個後臺執行緒便會將緩衝區的內容溢寫到磁碟中。再寫磁碟的過程中,map函式的輸出繼續被寫到緩衝區,但如果在此期間緩衝區被填滿,map會阻塞直到寫磁碟過程完成。寫磁碟會以輪詢的方式寫到mapred.local.dir(mapred-site.xml檔案的配置項)配置的作業特定目錄下。

在寫磁碟之前,執行緒會根據資料最終要傳入到的Reducer把緩衝區的資料劃分成(預設是按照鍵)相應的分割槽。在每個分割槽中,後臺執行緒按照建進行內排序,此時如果有一個Combiner,它會在排序後的輸出上執行。

一旦記憶體緩衝區達到溢位的閾值,就會新建一個溢位寫檔案,因此在Map任務完成最後一個輸出記錄之後,會有若干個溢位寫檔案。在Map任務完成之前,溢位寫檔案被合併成一個已分割槽且已排序的輸出檔案作為map輸出的中間結果,這也是Map任務的輸出結果。

如果已經指定Combiner且溢位寫次數至少為3時,Combiner就會在輸出檔案寫到磁碟之前執行。如前文所述,Combiner可以多次執行,並不影響輸出結果。執行Combiner的意義在於使map輸出的中間結果更緊湊,使得寫到本地磁碟和傳給Reducer的資料更少。

為了提高磁碟IO效能,可以考慮壓縮map的輸出,這樣會寫磁碟的速度更快,節約磁碟空間,從而使傳送給Reducer的資料量減少。預設情況下,map的輸出是不壓縮的,但只要將mapred-site.xml檔案的配置項mapred.compress.map.output設為true即可開啟壓縮功能。使用的壓縮庫由mapred-site.xml檔案的配置項mapred.map.output.compression.codec

指定,如下列出了目前hadoop支援的壓縮格式:

壓縮格式 工具 演算法 副檔名 是否包含多個檔案 是否可切分
DEFLATE* N/A DEFLATE .deflate
Gzip gzip DEFLATE .gz
bzip2 bzip2 bzip2 .bz2
LZO Lzop LZO .lzo

map輸出的中間結果儲存的格式為IFile,IFile是一種支援航壓縮的儲存格式,支援上述壓縮演算法。

Reducer通過Http方式得到輸出檔案的分割槽。將map輸出的中間結果傳送到Reducer的工作執行緒的數量由mapred-site.xml檔案的tasktracker.http.threds配置項決定,此配置針對每個節點,而不是每個Map任務,預設是40,可以根據作業大小,叢集規模以及節點的計算能力而增大。

4. shuffle

shuffle,也叫資料清洗。在某些語境下,代表map函式產生輸出到reduce的消化輸入的整個過程。

4.1 copy階段

Map任務輸出的結果位於Map任務的TaskTracker所在的節點的本地磁碟上。TaskTracker需要為這些分割槽檔案(map輸出)執行Reduce任務。但是,Reduce任務可能需要多個Map任務的輸出作為其特殊的分割槽檔案。每個Map任務的完成時間可能不同,當只要有一個任務完成,Reduce任務就開始複製其輸出。這就是shuffle的copy階段。如下圖所示,Reduce任務有少量複製執行緒,可以並行取得Map任務的輸出,預設值為5個執行緒,該值可以通過設定mapred-site.xml的mapred.reduce.parallel.copies的配置項來改變。

如果map輸出相當小,則會被複制到Reduce所在TaskTracker的記憶體的緩衝區中,緩衝區的大小由mapred-site.xml檔案中的mapred.job.shuffle.input.buffer.percent配置項指定。否則,map輸出將會被複制到磁碟。一旦記憶體緩衝區達到閾值大小(由mapred-site.xml檔案mapred.job.shuffle.merge.percent配置項決定)或緩衝區的檔案數達到閾值大小(由mapred-site.xml檔案mapred.inmem.merge.threshold配置項決定),則合併後溢寫到磁碟中。

4.2 sort階段

隨著溢寫到磁碟的檔案增多,shuffle進行sort階段。這個階段將合併map的輸出檔案,並維持其順序排序,其實做的是歸併排序。排序的過程是迴圈進行,如果有50個map的輸出檔案,而合併因子(由mapred-site.xml檔案的io.sort.factor配置項決定,預設為10)為10,合併操作將進行5次,每次將10個檔案合併成一個檔案,最後有5個檔案,這5個檔案由於不滿足合併條件(檔案數小於合併因子),則不會進行合併,將會直接把5個檔案交給Reduce函式處理。到此shuffle階段完成。

從shuffle的過程可以看出,Map任務處理的是一個InputSplit,而Reduce任務處理的是所有Map任務同一個分割槽的中間結果。

5. reduce及最後結果的輸出

reduce階段操作的實質就是對經過shuffle處理後的檔案呼叫reduce函式處理。由於經過了shuffle的處理,檔案都是按鍵分割槽且有序,對相同分割槽的檔案呼叫一次reduce函式處理。

與map的中間結果不同的是,reduce的輸出一般為HDFS。

6. sort

排序貫穿於Map任務和Reduce任務,排序操作屬於MapReduce計算框架的預設行為,不管流程是否需要,都會進行排序。在MapReduce計算框架中,主要用到了兩種排序演算法:快速排序和歸併排序。

在Map任務和Reduce任務的過程中,一共發生了3次排序操作。

(1)當map函式產生輸出時,會首先寫入記憶體的環形緩衝區,當達到設定的閾值,在刷寫磁碟之前,後臺執行緒會將緩衝區的資料劃分相應的分割槽。在每個分割槽中,後臺執行緒按鍵進行內排序。如下圖所示。

(2)在Map任務完成之前,磁碟上存在多個已經分好區,並排好序,大小和緩衝區一樣的溢寫檔案,這時溢寫檔案將被合併成一個已分割槽且已排序的輸出檔案。由於溢寫檔案已經經過一次排序,所以合併檔案時只需再做一次排序就可使輸出檔案整體有序。如下圖所示。

(3)在shuffle階段,需要將多個Map任務的輸出檔案合併,由於經過第二次排序,所以合併檔案時只需在做一次排序就可以使輸出檔案整體有序。

在這3次排序中第一次是在記憶體緩衝區做的內排序,使用的演算法是快速排序;第二次排序和第三次排序都是在檔案合併階段發生的,使用的是歸併排序。

7. 作業的進度組成

一個MapReduce作業在Hadoop上執行時,客戶端的螢幕通常會列印作業日誌,如下:

對於一個大型的MapReduce作業來說,執行時間可能會比較比較長,通過日誌瞭解作業的執行狀態和作業進度是非常重要的。對於Map來說,進度代表實際處理輸入所佔比例,例如 map 60% reduce 0% 表示Map任務已經處理了作業輸入檔案的60%,而Reduce任務還沒有開始。而對於Reduce的進度來說,情況比較複雜,從前面得知,reduce階段分為copy,sort 和 reduce,這三個步驟共同組成了reduce的進度,各佔1/3。如果reduce已經處理了2/3的輸入,那麼整個reduce的進度應該為1/3 + 1/3 + 1/3 * (2/3) = 5/9 ,因為reduce開始處理時,copy和sort已經完成。

 

 來源於:《Hadoop 海量資料處理》

轉: http://blog.csdn.net/sunnyyoona/article/details/53939546

相關文章