Hadoop大資料實戰系列文章之Mapreduce 計算框架

testingbang發表於2020-11-10

如果將Hadoop比做一頭大象,那麼MapReduce就是那頭大象的電腦。MapReduce是 Hadoop 核心程式設計模型。在 Hadoop 中,資料處理核心就是 MapReduce 程式設計模型。

本章內容:

1) MapReduce 程式設計模型

2) MapReduce 執行流程

3) MapReduce 資料本地化

4) MapReduce 工作原理

5) MapReduce 錯誤處理機制


1. MapReduce 程式設計 模型

Map和Reduce的概念是從函式式變成語言中借來的,整個MapReduce計算過程分為 Map 階段和Reduce階段,也稱為對映和縮減階段,這兩個獨立的階段實際上是兩個獨立的過程,即 Map 過程和 Reduce 過程,在 Map 中進行資料的讀取和預處理,之後將預處理的結果傳送到 Reduce 中進行合併。

我們透過一個程式碼案例,讓大家快速熟悉如何透過程式碼,快速實現一個我們自己的MapReduce。

案例:分散式計算出一篇文章中的各個單詞出現的次數,也就是 WordCount。

1) 建立 map.py 檔案,寫入以下程式碼:

#!/usr/bin/env pythonimport sysword_list = []for line in sys.stdin:
  word_list = line.strip().split(' ')
  if len(word_list) <= 0:
    continue
  for word in word_list:
    w = word.strip()
    if len(w) <= 0:
      continue
    print '\t'.join([w, "1"])

該程式碼主要工作是從文章資料來源逐行讀取,文章中的單詞之間以空格分割,

word_list = line.strip().split(' ')這塊程式碼是將當前讀取的一整行資料按照空格分割,將分割後的結果存入 word_list 陣列中,然後透過 for word in word_list 遍歷陣列,取出每個單詞,後面追加“1”標識當前 word 出現 1 次。

2) 建立 reduce.py,寫入以下程式碼:

#!/usr/bin/env pythonimport syscur_word = Nonesum_of_word = 0for line in sys.stdin:
  ss = line.strip().split('\t')
  if len(ss) != 2:
    continue
  word = ss[0].strip()
  count = ss[1].strip()
  if cur_word == None:
    cur_word = word
  if cur_word != word:
    print '\t'.join([cur_word, str(sum_of_word)])
    sum_of_word = 0
    cur_word = word
  sum_of_word += int(count)print '\t'.join([cur_word, str(sum_of_word)])sum_of_word = 0

該程式碼針對 map 階段的陣列進行彙總處理,map 到 reduce 過程中預設存在shuffle partition 分組機制,保證同一個 word 的記錄,會連續傳輸到 reduce 中,所以在 reduce階段只需要對連續相同的 word 後面的技術進行累加求和即可。

3) 本地模擬測試指令碼:

$ cat big.txt | python map.py | sort -k1 | python reduce.pycat 1run 3see 2spot 2the 1

6) 指令碼執行流程:

see spot runrun spot runsee the catsee spot runsee the catrun spot runsee,1spot,1run,1run,1spot,1run,1see,1the,1cat,1see,1see,1spot,1spot,1run,1run,1run,1the,1cat,1see,1spot,1run,1the,1cat,1cat 1run 3see 2spot 2the 1
Hadoop大資料實戰系列文章之Mapreduce 計算框架

2. MapReduce 執行流程

上面的例子屬於 MapReduce 計算框架的一般流程,經過整理總結:

Hadoop大資料實戰系列文章之Mapreduce 計算框架

1) 輸入和拆分:

不屬於 map 和 reduce 的主要過程,但屬於整個計算框架消耗時間的一部分,該部分會為正式的 map 準備資料。


分片(split)操作:

split 只是將原始檔的內容分片形成一系列的 InputSplit,每個 InputSpilt中儲存著對應分片的資料資訊(例如,檔案塊資訊、起始位置、資料長度、所在節點列表…),並不是將原始檔分割成多個小檔案,每個 InputSplit 都由一個 mapper 進行後續處理。

每個分片大小引數是很重要的,splitSize 是組成分片規則很重要的一個引數,該引數由三個值來確定:

 minSize:splitSize 的最小值,由 mapred-site.xml 配置檔案中

mapred.min.split.size 引數確定。

 maxSize:splitSize 的最大值,由 mapred-site.xml 配置檔案中

mapreduce.jobtracker.split.metainfo.maxsize 引數確定。

 blockSize:HDFS 中檔案儲存的快大小,由 hdfs-site.xml 配置檔案中

dfs.block.size 引數確定。

splitSize 的確定規則:splitSize=max{minSize,min{maxSize,blockSize}}

資料格式化(Format)操作:

將劃分好的 InputSplit 格式化成鍵值對形式的資料。其中key為偏移量,value 是每一行的內容。

值得注意的是,在 map 任務執行過程中,會不停的執行資料格式化操作,每生成一個鍵值對就會將其傳入 map,進行處理。所以 map和資料格式化操作並不存在前後時間差,而是同時進行的。

Hadoop大資料實戰系列文章之Mapreduce 計算框架

2) Map 對映:

是 Hadoop 並行性質發揮的地方。根據使用者指定的 map過程,MapReduce 嘗試在資料所在機器上執行該 map 程式。在 HDFS 中,檔案資料是被複制多份的,所以計算將會選擇擁有此資料的最空閒的節點。

在這一部分,map 內部具體實現過程,可以由使用者自定義。

3) Shuffle 派發:

Shuffle 過程是指 Mapper 產生的直接輸出結果,經過一系列的處理,成為最終的Reducer直接輸入資料為止的整個過程。這是mapreduce的核心過程。該過程可以分為兩個階段:

Mapper 端的 Shuffle:由 Mapper 產生的結果並不會直接寫入到磁碟中,而是先儲存在記憶體中,當記憶體中的資料量達到設定的閥值時,一次性寫入到本地磁碟中。並同時進行sort(排序)、combine(合併)、partition(分片)等操作。其中,sort 是把 Mapper 產生的結果按照key值進行排序;combine是把key值相同的記錄進行合併;partition是把資料均衡的分配給 Reducer。

Reducer 端的Shuffle:由於Mapper 和Reducer往往不在同一個節點上執行,所以Reducer 需要從多個節點上下載 Mapper 的結果資料,並對這些資料進行處理,然後才能被 Reducer 處理。

4) Reduce 縮減:

Reducer接收形式的資料流,形成形式的輸出,具體的過程可以由使用者自定義,最終結果直接寫入 hdfs。每個 reduce 程式會對應一個輸出檔案,名稱以 part-開頭。

3. MapReduce 資料 本地化(Data-Local )

首先,HDFS 和 MapReduce 是 Hadoop 的核心設計。對於 HDFS,是儲存基礎,在資料層面上提供了海量資料儲存的支援。而 MapReduce,是在資料的上一層,透過編寫MapReduce 程式對海量資料進行計算處理。

在前面 HDFS 章節中,知道了 NameNode 是檔案系統的名位元組點程式,DataNode是檔案系統的資料節點程式。

MapReduce計算框架中負責計算任務排程的JobTracker對應HDFS的NameNode的角色,只不過一個負責計算任務排程,一個負責儲存任務排程。

MapReduce計算框架中負責真正計算任務的TaskTracker對應到HDFS的DataNode的角色,一個負責計算,一個負責管理儲存資料。

考慮到“本地化原則”,一般地,將 NameNode 和 JobTracker 部署到同一臺機器上,各個 DataNode 和 TaskNode 也同樣部署到同一臺機器上。

Hadoop大資料實戰系列文章之Mapreduce 計算框架

這樣做的目的是將 map 任務分配給含有該 map 處理的資料塊的 TaskTracker上,同時將程式 JAR 包複製到該 TaskTracker 上來執行,這叫“運算移動,資料不移動”。而分配reduce 任務時並不考慮資料本地化。

4. MapReduce 工作 原理

我們透過 Client、JobTrask 和 TaskTracker 的角度來分析 MapReduce 的工作原理:

Hadoop大資料實戰系列文章之Mapreduce 計算框架

首先在客戶端(Client)啟動一個作業(Job),向 JobTracker 請求一個 Job ID。將執行作業所需要的資原始檔複製到 HDFS 上,包括 MapReduce 程式打包的 JAR檔案、配置檔案和客戶端計算所得的輸入劃分資訊。這些檔案都存放在JobTracker專門為該作業建立的檔案 夾中, 檔案 夾名為 該作 業的 Job ID。 JAR 檔案 預設 會有 10 個副 本

(mapred.submit.replication屬性控制);輸入劃分資訊告訴了JobTracker應該為這個作業啟動多少個 map 任務等資訊。

JobTracker 接收到作業後,將其放在一個作業佇列裡,等待作業排程器對其進行排程當作業排程器根據自己的排程演算法排程到該作業時,會根據輸入劃分資訊為每個劃分建立一個 map 任務,並將 map 任務分配給 TaskTracker 執行。對於 map 和 reduce 任務,TaskTracker 根據主機核的數量和記憶體的大小有固定數量的 map 槽和 reduce 槽。這裡需要強調的是:map 任務不是隨隨便便地分配給某個 TaskTracker 的,這裡就涉及到上面提

到的資料本地化(Data-Local)。

TaskTracker每隔一段時間會給JobTracker傳送一個心跳,告訴JobTracker它依然在執行,同時心跳中還攜帶著很多的資訊,比如當前 map 任務完成的進度等資訊。當JobTracker收到作業的最後一個任務完成資訊時,便把該作業設定成“成功”。當JobClient查詢狀態時,它將得知任務已完成,便顯示一條訊息給使用者。

Hadoop大資料實戰系列文章之Mapreduce 計算框架

如果具體從 map 端和 reduce 端分析,可以參考上面的圖片,具體如下:

Map 端流程:

1) 每個輸入分片會讓一個 map 任務來處理,map 輸出的結果會暫且放在一個環形記憶體緩衝區中(該緩衝區的大小預設為 100M,由 io.sort.mb 屬性控制),當該緩衝區快要溢位時(預設為緩衝區大小的80%,由io.sort.spill.percent屬性控制),會在本地檔案系統中建立一個溢位檔案,將該緩衝區中的資料寫入這個檔案。

2) 在寫入磁碟之前,執行緒首先根據reduce任務的數目將資料劃分為相同數目的分割槽,也就是一個reduce任務對應一個分割槽的資料。這樣做是為了避免有些reduce任務分配到大量資料,而有些reduce任務卻分到很少資料,甚至沒有分到資料的尷尬局面。其實分割槽就是對資料進行hash的過程。然後對每個分割槽中的資料進行排序,如果此時設定了 Combiner,將排序後的結果進行 Combine 操作,這樣做的目的是讓儘可能少的資料寫入到磁碟。

3) 當 map 任務輸出最後一個記錄時,可能會有很多的溢位檔案,這時需要將這些檔案合併。合併的過程中會不斷地進行排序和 Combine 操作,目的有兩個:

 儘量減少每次寫入磁碟的資料量;

 儘量減少下一複製階段網路傳輸的資料量。

最後合併成了一個已分割槽且已排序的檔案。為了減少網路傳輸的資料量,這裡可以將資料壓縮,只要將 mapred.compress.map.out 設定為 true 就可以了。

4) 將分割槽中的資料複製給相對應的 reduce 任務。分割槽中的資料怎麼知道它對應的reduce 是哪個呢?其實 map 任務一直和其父 TaskTracker 保持聯絡,而TaskTracker又一直和JobTracker保持心跳。所以JobTracker中儲存了整個叢集中的宏觀資訊。只要reduce任務向JobTracker獲取對應的map輸出位置就可以了。

Reduce端流程:

1) Reduce 會接收到不同 map 任務傳來的資料,並且每個 map 傳來的資料都是有序的。如果 reduce 端接受的資料量相當小,則直接儲存在記憶體中(緩衝區大小由mapred.job.shuffle.input.buffer.percent屬性控制,表示用作此用途的堆空間的百 分 比 ), 如 果 數 據 量 超 過 了 該 緩 衝 區 大 小 的 一 定 比 例 ( 由

mapred.job.shuffle.merge.percent 決定),則對資料合併後溢寫到磁碟中。

2) 隨著溢寫檔案的增多,後臺執行緒會將它們合併成一個更大的有序的檔案,這樣做是為了給後面的合併節省時間。其實不管在 map 端還是 reduce 端,MapReduce都是反覆地執行排序,合併操作,所以排序是 hadoop 的靈魂。

3) 合併的過程中會產生許多的中間檔案(寫入磁碟了),但MapReduce會讓寫入磁碟的資料儘可能地少,並且最後一次合併的結果並沒有寫入磁碟,而是直接輸入到reduce 函式。

在 Map 處理資料後,到Reduce得到資料之前,這個流程在MapReduce中可以看做是一個 Shuffle 的過程。

在經過 mapper 的執行後,我們得知 mapper 的輸出是這樣一個 key/value 對。到底當前的 key 應該交由哪個 reduce 去做呢,是需要現在決定的。 MapReduce 提供Partitioner 介面,它的作用就是根據 key 或value 及 reduce的數量來決定當前的這對輸出資料最終應該交由哪個 reduce task 處理。預設對 key 做 hash 後再以 reduce task 數量取模。預設的取模方式只是為了平均 reduce 的處理能力,如果使用者自己對 Partitioner有需求,可以訂製並設定到 job 上。

5. MapReduce 錯誤 處理機制

MapReduce任務執行過程中出現的故障可以分為兩大類:硬體故障和任務執行失敗引發的故障。

1) 硬體故障

在 Hadoop Cluster 中,只有一個 JobTracker,因此,JobTracker 本身是存在單點故障的。如何解決JobTracker的單點問題呢?我們可以採用主備部署方式,啟動JobTracker主節點的同時,啟動一個或多個 JobTracker 備用節點。當 JobTracker主節點出現問題時,透過某種選舉演算法,從備用的 JobTracker 節點中重新選出一個主節點。

機器故障除了 JobTracker 錯誤就是 TaskTracker 錯誤。TaskTracker 故障相對較為常見,MapReduce 通常是透過重新執行任務來解決該故障。

在 Hadoop 叢集中,正常情況下,TaskTracker 會不斷的與 JobTracker 透過心跳機制進行通訊。如果某 TaskTracker 出現故障或者執行緩慢,它會停止或者很少向 JobTracker傳送心跳。如果一個TaskTracker在一定時間內(預設是1分鐘)沒有與JobTracker通訊,那麼 JobTracker 會將此 TaskTracker 從等待任務排程的 TaskTracker 集合中移除。同時JobTracker 會要求此 TaskTracker 上的任務立刻返回。如果此 TaskTracker 任務仍然在

mapping 階段的Map任務,那麼JobTracker會要求其他的TaskTracker重新執行所有原本由故障 TaskTracker 執行的 Map 任務。如果任務是在 Reduce 階段的 Reduce 任務,那麼JobTracker會要求其他TaskTracker重新執行故障TaskTracker未完成的Reduce任務。

比如:一個 TaskTracker 已經完成被分配的三個 Reduce 任務中的兩個,因為 Reduce 任務一旦完成就會將資料寫到 HDFS 上,所以只有第三個未完成的 Reduce 需要重新執行。但是對於 Map 任務來說,即使TaskTracker 完成了部分Map,Reduce仍可能無法獲取此節點上所有 Map 的所有輸出。所以無論Map 任務完成與否,故障TaskTracker上的Map 任務都必須重新執行。

2) 任務執行失敗引發的故障

在實際任務中,MapReduce作業還會遇到使用者程式碼缺陷或程式崩潰引起的任務失敗等情況。使用者程式碼缺陷會導致它在執行過程中丟擲異常。此時,任務 JVM 程式會自動退出,並向 TaskTracker 父程式傳送錯誤訊息,同時錯誤訊息也會寫入 log 檔案,最後 TaskTracker將此次任務嘗試標記失敗。對於程式崩潰引起的任務失敗,TaskTracker的監聽程式會發現程式退出,此時TaskTracker也會將此次任務嘗試標記為失敗。對於死迴圈程式或執行時間太長的程式,由於 TaskTracker 沒有接收到進度更新,它也會將此次任務嘗試標記為失敗,並殺死程式對應的程式。

在以上情況中,TaskTracker將任務嘗試標記為失敗之後會將TaskTracker自身的任務計數器減 1,以便想 JobTracker 申請新的任務。TaskTracker 也會透過心跳機制告訴JobTracker本地的一個任務嘗試失敗。JobTracker接到任務失敗的通知後,透過重置任務狀態,將其加入到排程佇列來重新分配該任務執行(JobTracker 會嘗試避免將失敗的任務再次分配給執行失敗的 TaskTracker)。如果此任務嘗試了 4 次(次數可以進行設定)仍沒有完成,就不會再被重試,此時整個作業也就失敗了


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

相關文章