06 MapReduce工作机制

来源:互联网 发布:js判断大于0的整数 编辑:程序博客网 时间:2024/05/29 06:26

MapReduce作业的执行流程

1、提交作業

在提交JobConf對象之後,用戶程序調用JobClient的runJob方法

runJob方法會先行調用JobSubmissionProtocol接口所定義的submitJob方法,並將作業提交給JobTracker。

緊接着,runJob不斷循環,並在循環中調用JobSumissionProtocol的getTaskCompletionEvents方法,獲取TaskCompletionEvent類的對象實例,了解作業的實時執行情況。如果發現作業運行狀態有更新,就將狀態報告給JobTracker。

作業完成後,如果成功則顯示作業計數器,否則,將導致作業失敗的錯誤記錄到控制台。


submitJob方法所做的工作:

1)從JobTracker處獲取當前作業ID號;

2)檢查作業的輸入輸出路徑;

3)計算作業的輸入劃分,並將劃分信息寫入Job.split文件,如果寫入失敗就會返回錯誤。split信息包括文件名,此split在文件中的起始位置,split的location信息(位於哪個節點)。

4)將運行作業所需要的資源——JAR文件、配置文件、輸入劃分等——複製到作業對應的HDFS上;

5)調用JobTracker對象的submitJob()方法真正提交作業。


2、初始化作業

在客戶端用戶作業調度JobTracker對象的submitJob()方法後,JobTracker會把此調用放入內部的TaskScheduler變量中,然後進行調度,默認的調度方法是JobQueueTaskScheduler,也就是FIFO調度方式。

當客戶作業被調度執行時,JobTracker會創建一個代表這個作業的JobInProgress對象,並將任務和記錄信息封裝到這個對象中,以便跟蹤任務的狀態和進程。

接下來JobInProgress對象的initTasks函數會對任務進行初始化操作。

初始化所做的工作:

1)從HDFS中讀取作業對應的job.split,爲後面的Map任務的分配做好準備;

2)創建並初始化Map任務和Reduce任務。

initTasks先根據輸入數據劃分信息中的個數設定Map Task的個數,然後爲每個Map Task生成一個TaskInProgress來處理input split,並將Map Task放入nonRunningMapCache,以便在JobTracker向TaskTracker分配Map Task的時候使用。

接下來根據JobConf中的mapred.reduce.tasks屬性利用setNumReduceTasks()方法來設置reduce tasks的個數,然後採用類似Map Task的方式將Reduce Task放入nonRunningReduces中,以便向TaskTracker分配Reduce Task時使用。

3)創建兩個初始化Task,根據個數和輸入劃分已經配置的信息,分別初始化Map和Reduce。


3、分配任務

TaskTracker和JobTracker之間的通信和任務的分配是通過心跳機制完成的。

TaskTracker作爲一個單獨的JVM執行一個簡單的循環,主要實現每隔一段時間向JobTracker發送心跳(Heartbeat):告訴JobTracker此TaskTracker是否存活,是否準備執行新的任務。

JobTracker接收到心跳信息,如果有待分配任務,它就會爲TaskTracker分配一個任務,並將分配信息封裝在心跳通信的返回值中返回給TaskTracker。

TaskTracker從心跳方法的Response中得知此TaskTracker需要做的事情,如果是一個新的Task則將它加入本機的任務隊列中。


TaskTracker首先發送自己的狀態(主要是Map任務和Reduce任務的個數是否小於上限),並根據自身條件選擇是否向JobTracker請求新的Task,最後發送心跳。

JobTracker接收到TaskTracker的心跳後首先分析心跳信息,如果發現TaskTracker在請求一個Task,那麼任務調度器就會將任務和任務信息封裝起來返回給TaskTracker。


針對Map任務和Reduce任務,TaskTracker有固定數量的任務槽(Map任務和Reduce任務的個數都有上限)。

當TaskTracker從JobTracker返回的心跳信息中獲取新的任務信息時,它會將Map任務或者Reduce任務加入對應的任務槽中。

在JobTracker爲TaskTracker分配Map任務時,爲了減小網絡帶寬,會考慮將map任務數據本地化。它會根據TaskTracker的網絡位置,選取一個距離此TaskTracker map任務最近的輸入劃分文件分配給此TaskTracker。

最好的情況是,劃分文件就在TaskTracker本地。


4、執行任務

TaskTracker申請到新的任務之後,就要在本地運行任務了。

運行任務的第一步是將任務本地化(將任務運行所必須的數據、配置信息、程序代碼從HDFS複製到TaskTracker本地)。

主要通過調用localizeJob()方法來完成的,工作如下:

1)將job.split複製到本地;

2)將job.jar複製到本地;

3)將job的配置信息寫入job.xml;

4)創建本地任務目錄,解壓job.jar;

5)調用launchTaskForJob()方法發佈任務。


任務本地化後,可以通過調用launchTaskForJob()真正啓動起來。

lauchTaskForJob()又會調用launchTask()方法啓動任務。

launchTask()方法首先爲任務創建本地目錄,然後啓動TaskRunner。

在啓動TaskRunner後,對於Map任務,會啓動MapTaskRunner;對於Reduce任務則啓動ReduceTaskRunner。


之後,TaskRunner又會啓動新的Java虛擬機來運行每個任務。

以Map任務爲例,任務執行的簡單流程是:

1)配置任務執行參數(獲取Java程序的執行環境和配置參數等);

2)在Child臨時文件表中添加Map任務信息(運行Map和Reduce任務的主進程是Child類);

3)配置log文件夾,然後配置Map任務的通信和輸出參數;

4)讀取input split,生成RecordReader讀取數據;

5)爲Map任務生成MapRunnable,依次從RecordReader中接收數據,並調用Mapper的Map函數進行處理;

6)最後將Map函數的輸出調用collect收集到MapOutputBuffer中。


5、更新任務執行進度和狀態

MapReduce作業是一個長時間運行的批量作業,有時候可能需要運行數小時。

總體來講,MapReduce作業的進度由下面幾項組成:

Mapper或Reducer讀入或寫出一條記錄,在報告中設置狀態描述,增加計數器,調用Reporter對象的progress()方法。


由MapReduce作業分割成的每個任務都有一組計數器,它們對任務執行過程中的進度組成事件進行計數。

如果任務要報告進度,它便會設置一個標誌以表明狀態變化將會發送到TaskTracker上。

另一個監聽線程檢查到這標誌後,會告知TaskTracker當前的任務狀態。


同時,TaskTracker在每隔5秒發送給JobTracker的心跳中封裝任務狀態,報告自己的任務執行狀態。


通過心跳通信機制,所有TaskTracker的統計信息都會匯總到JobTracker處。

JobTracker將這些信息合併產生一個全局作業進度統計信息。

最後,JobClient通過每秒查看JobTracker來接收作業進度的最新狀態。


6、完成作業

當JobTracker接收到最後一個任務的已完成通知後,便把作業狀態設置爲成功。

JobClient也將及時告知用戶作業已完成。

最後從runJob()方法處返回。

在返回後,JobTracker會清空作業的工作狀態,並指示TaskTracker也清空作業的工作狀態,比如刪除中間輸出等。



错误处理机制

Hadoop利用冗餘數據來解決硬件故障,以保證數據安全和任務執行。


硬件故障:

JobTracker機器故障——HA

TaskTracker機器故障:重新執行任務


任務失敗:

用戶代碼缺陷或者進程崩潰引起的任務失敗。


用戶代碼缺陷會導致它在執行過程中拋出異常。此時JVM進程會自動退出,並向TaskTracker父進程發送錯誤消息,同時錯誤消息也會寫入log文件,最後TaskTracker將此次任務嘗試標記失敗。

對於進程崩潰引起的任務失敗i,TaskTracker的監聽程序會發現進程退出,此時TaskTracker也會將此次任務嘗試標記爲失敗。

對於死循環程序或執行時間太長的程序,由於TaskTracker沒有接收到進度更新,它也會將此次任務嘗試標記爲失敗,並殺死程序對應的進程。


在以上情況中,TaskTracker將任務嘗試標記爲失敗之後會將TaskTracker自身的任務計數器減1,以便向JobTracker申請新的任務。

TaskTracker也會通過心跳機制告訴JobTracker本地的一個任務嘗試失敗。

JobTracker接到任務失敗的通知後,通過重置任務狀態,將其加入到調度隊列來重新分配該任務執行。

JobTracker會嘗試避免將失敗的任務再次分配給運行失敗的TaskTracker。

如果此任務嘗試了4次(次數可以進行設置)仍沒有完成,則整個任務失敗。


Shuffle和排序

Shuffle過程的性能與整個MapReduce的性能直接相關。

Shuffle過程包含在Map和Reduce兩端中。

在Map端是對Map的結果進行partition, sort, spill,然後merge屬於同一個劃分的輸出並寫在磁盤上,同時按照不同的劃分將結果發送給對應的Reduce(Map輸出的劃分與Reduce的對應關係由JobTracker確定)。

Reduce端又會將各個Map送來的屬於同一個劃分的輸出進行合併(merge),然後對merge的結果進行排序,最後交給Reduce處理。


Map端

Map的輸出結果是由collector函數處理的。

當輸出內存緩衝區內容達到設定的閾值時,把緩衝取內容spill到磁盤中。

collector函數將緩衝區中的內容寫出時,會調用sortAndSpill函數。

sortAndSpill每被調用一次就會創建一個spill文件,然後按照key對需要寫出的數據進行排序,最後按照劃分的順序將所有需要寫出的結果寫入這個spill文件中。

如果用戶作業配置了combiner類,那麼在寫出過程中會先調用combineAndSpill()再寫出,對結果進一步合併,是爲了讓Map的輸出數據更加緊湊。

Map任務結束之後,用mergeParts()將所有的spill文件中的數據按照劃分重新組織,以便Reduce處理。


Reduce端

分成三個階段:複製Map輸出、排序合併、Reduce處理。

Reduce定期向JobTracker獲取Map的輸出位置。一旦拿到位置,Reduce任務就會從此輸出對應的TaskTracker上複製輸出到本地,而不是等到所有的Map任務結束。如果Map的輸出很小,則會被複製到執行Reduce任務的TaskTracker節點的內存中,否則會放入磁盤中。

在Reduce複製Map的輸出結果的同時,Reduce任務就進入了合併(merge)階段。這一階段主要的任務是將從各個Map TaskTracker上複製的Map輸出文件進行整合,並維持數據原來的順序。

reduce端的最後階段就是對合併的文件進行reduce處理。


shuffle過程的優化

在一個任務中,完成單位任務使用時間最多的一般都是IO操作。

在Map端,主要就是shuffle階段中緩衝區內容超過閾值後的寫出操作。可以增加io.sort.mb的值來減少寫出次數。

在Reduce端,在複製Map輸出的時候直接將複製的結果放在內存中同樣能夠提升性能,前提是留下的內存足夠Reduce任務執行。所以在Reduce函數的內存需求很小的情況下,將mapred.inmem.merge.threshold設置爲0,將mapred.job.reduce.input.buffer.percent設置爲1.0或更低能夠讓IO操作更少,提升shuffle性能。


任务执行細節


當JobTracker檢測到所有任務中存在運行時過於緩慢的任務時,就會啓動另一個相同的任務作爲備份。原始任務和備份任務中只要有一個完成,另一個就會被終止。

默認開啓,可通過

mapred.map.tasks.speculative.execution

mapred.reduce.tasks.speculative.execution

屬性值來爲Map和Reduce任務開啓或關閉這個功能。


任務JVM重用

當TaskTracker被分配一個任務時,就會在本地啓動一個新的JVM來運行這個任務。

對於有大量零碎輸入文件的Map任務而言,爲每一個Map任務啓動一個JVM是有改善空間的,即讓後續的任務重用此JVM,這樣就省下新任務啓動新的JVM的時間。

可通過mapred.job.reuse.jvm.num.tasks配置。默認1,不重用;設置爲大於1的數來啓動重用;設置爲-1表示共享此JVM的任務數目不受限制。


跳過壞記錄

當skipping模式啓動時,如果任務連續失敗兩次,他會將自己正在處理的記錄告訴TaskTracker,然後TaskTracker會重新運行該任務並在運行到先前任務報告的記錄時直接跳過。

skipping模式只能跳過一條錯誤記錄。

mapred.map.max.attemps和mapred.reduce.max.attemps兩個屬性可以增加跳過的錯誤記錄個數。

此模式默認關閉,可通過SkipBadRecord類單獨爲Map和Reduce任務啓動它。



0 0
原创粉丝点击