詳解MapReduce工作流程

 一、客戶端向JobTracker提交作業

這個階段要完成以下工作:

  • 向JobTracker申請 一下新的JobID
  • 檢查是否指定了output dir,並且確認output dir不存在
  • 根據InputPath計算input split。這裏的input split並不是MapReduce輸入數據的完整拷貝,只是記錄了每個split在什麼地方存放着。split和block一樣都是邏輯概念,一個split可能跨越不同的磁盤。
  • 把運行作業所需的resource複製到jobtracker的文件系統上去,保存在一個包含jobID的目錄下。這些resource包括:jar文件、配置文件、計算好的input split等。
  • 告訴JobTracker已經ready for execution。
public interface InputSplit extends Writable {
        long getLength() throws IOException;
        String[] getLocations() throws IOException;
}

我們看到InputSplit中記錄了原始數據的長度length,而location則有多個(是一個數組)。location只記錄了主機名,它用於在指派map task的時候,讓map task離它處理的split近一些。而記錄length的作用是讓最大的split先被處理,這是hadoop爲了最小化作業運行時間而採取的貪心策略。

二、作業初始化

jobtracker根據input split從HDFS中獲取每個split代表的真實數據,對於每個split都要生成一個map task來處理它。至於生成多少reduce task則由屬性mapred.reduce.tasks來決定。每個task都獲得一個ID。一個map task和它要處理的split在集羣中儘可能地鄰近,最好是在同一個TaskTracker上。

三、任務分配

只要task tracker一啓動它就週期性地向job tracker發送心跳,表示自己還活着,心跳中還包含其他信息,告訴job tracer自己的忙閒狀態,如果是閒,那job tracker就可以給它分配新任務了。

每個TaskTracker上都有固定數目(這取決於該節點擁有的CPU和內存)的slots來安放map task和reduce task。比如有的TaskTracker上可以同時運行2個map task和2個reduce task。

四、執行任務

首先每個task tracker要從共享的filesystem中拷貝兩樣東西到本地:包含程序代碼的jar文件、DistributedCacheFile。然後爲本次task創建一個工作目錄。最後創建一個JVM運行作業。也就是說在task tracker上可以同時運行多個JVM,每個JVM運行一個task(map tack或reduce task) ,所以在map或reduce中發生的bug只會影響本JVM(使之中止或崩潰),但不會影響到task tracker。JVM在不同的task之間是可以重用的,但是一個JVM在同一時刻只能運行一個task。

五、更新進度和狀態

每個task都要及時地向它所在的TaskTracker彙報進度和狀態,TaskTracker及時地向JobTracker彙報執行進度和狀態(比如執行了多少個task,哪個失效了等等),這些信息最終由JobTracker反饋給用戶。

解釋一下“進度”,對於map task來說,就是已處理的數據和input split總長度的比值;但是對於reduce task來說就有些特別,事實上reduce task包括3個階段:copy、sort和reduce,每完成一個階段進度就前進了33%。

六、完成任務

JobTracker清理一些狀態數據和臨時的輸出文件,並通知TaskTacker做同樣的清理工作。

七、失效

對於懸掛的task,如果task tracker在一段時間(默認是10min,可以通過mapred.task.timeout屬性值來設置,單位是毫秒)內一直沒有收到它的進度報告,則把它標記爲失效。TaskTracker通過心跳包告知JobTracker某個task attempt失敗了,則JobTracker把該task儘量分配給另外一個task tracker來執行。如果同一個task連續4次(該值可以通過mapred.map.max.attempts和mapred.reduce.max.attempts屬性值來設置)都執行失敗,那JobTracker就不會再做更多的嘗試了,本次job也就宣告失敗了。

對於某些應用可能不想因爲個別task的失敗而導致整個job的失敗,你可以設置失效的task小於一定比例時job仍然是成功的,通過這兩個屬性:mapred.max.map.failures.percent和mapred.max.reduce.failures.percent。

上面說了task失效,還有一種情況是TaskTracker失效。如果TaskTracker運行很慢或直接crash了,則它停止向jobtracker發送心跳。若JobTracker在10min(這個值可以通過mapred.task.tracker.expiry.interval屬性進行設置)沒有收到TaskTracker的心跳就把它從調度池中移除。由於整個TaskTracker節點都失效,它上面的已經執行完畢的map task的輸出也不能再被reduce task獲取,所以之前分配給該TaskTracker的所有map task(不管是執行完畢還是沒有完畢)都要放到另一個節點上重新執行,已經執行完畢的reduce task由於結果已經輸出到了最終文件裏面,就不需要重新執行了。即使TaskTracker沒有失效,當它上面失效的task太多時同樣會被列入黑名單。

如果失效的是JobTracker那就無藥可救了,因爲JobTracker只有一個,沒有誰可以替代它。

八、作業調度

默認情況下采用FIFO原則,先提交的作業先被調度。其實job也可以設置優先級,通過mapred.job.priority屬性或調用setJobPriority()方法。

“公平調度器”讓每個job獲得相同的資源,所以小作業會比大作業完成得早。

九、洗牌和排序

在Map側

每個map task都有一個循環利用的緩衝區(默認大小是100M,通過io.sort.mb設置),它有輸出先放到緩衝區裏。當緩衝區滿80%(該值可以通過io.sort.spill.percent設置)後,後臺線程會把它spill到磁盤。當緩衝區填滿100%時,map的輸出沒地方做,map task將被阻塞。

在被寫入磁盤之前,數據首先會被分區(根據hadoop的Partitioner),在每個分區中數據會被放到內存中進行排序。MapTask結束的時候所有的分區會被合併成一個排序好的文件。

在Reduce側

map的輸出就在本節點上,而reduce的則不是。上文已提到過,reduce task包括3個階段:copy、sort和reduce。Reducer有數個並行的copy線程,每當有map task結束時,它們就把map的輸出拷貝過來。那reducer怎麼會知道有mapper結束呢?因爲mapper的執行進度jobtracker都會知道,reducer中有個線程專門負責向jobtracker詢問哪個mapper結束了。mapper的輸出是在整個job結束後才被刪除的,mapper的輸出被reducer拷貝之後並不會立即被刪除,因爲reducer可能會失效。

不需要等到所有的mapper都結束,當reducer已經拷貝了幾個mapper的輸出到本地後,它就開始把它們合併成一個更大的文件。當所有的mapper都結束時,reducer執行最後一次merge sort。這就是sort階段(實際上sort在map側都已經執行了,在reduce側執行的是歸併排序)。

至於reduce階段自然就是執行reduce()函數嘍.

由於shuffle要佔用大量內存,所以我們設計的map和reduce要儘量地小,佔用少量的內存爲好。

十、Speculative Task

一個task執行得慢可能出於多種原因,包括硬件老化。hadoop的策略是當發現task執行得慢時就在一個新的JVM甚至是新的節點上啓動它的一個“備份”。

當所有的task都已經啓動並執行了一段時間後,如果發現某些task比其他的運行的都慢,這時就會啓動一個speculatie task跟原task執行相同的任務--只要其中有一個先完成,那另一個就可以kill掉了。

當然採用speculatie task就會產生雙份的輸出,要處理好這個問題。

十、跳過壞記錄

對於海量的數據文件,存在壞記錄(格式不對、數據缺失等)是很正常的,在程序中必須妥善處理,否則可能會因爲極個別的“壞記錄”中斷了整個Job。另外你也可以選擇skip bad records。Skipping模式默認情況下是關閉的,你可以使用SkipBadRecord類分別爲map和reduce打開Skipping模式。但是Skipping模式默認情況下對於每個task只能跳過1壞記錄而不妨礙task的成功結束,你可以通過設置mapred.map.max.attempts和mapred.reduce.max.attempts來把容忍值調得大一些。

 

Hadoop可以爲task提供運行時的環境信息:

mapred.job.id    JobID

mapred.tip.id    TaskID

mapred.task.id    task attempt ID,注意的task ID區別

mapred.task.partition    task ID在Job中的序號

mapred.task.is.map    判斷task是否爲map task

由於task可能會失效,這樣一個task就會對應多個task attempt,就會有多次輸出。這瞭解決這個問題,每次task attempt的輸出目錄爲${mapred.output.dir}/_temporary/${mapred.task.id},當任務成功結束後再把它複製到${mapred.output.dir}中。那一個task如何知道自己的工作目錄呢?可以從配置文件中檢索mapred.word.output.dir的屬性值,也可調用FileOutputFormat的靜態方法getWorkOutputPath()。

原文來自:博客園(華夏35度)http://www.cnblogs.com/zhangchaoyang 作者:Orisun

發佈了17 篇原創文章 · 獲贊 19 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章