任何一個作業在Hadoop集羣中執行主要包括四個階段:setup、map、reduce、cleanup,但在這四個階段都出現任務實例在TaskTracker節點執行失敗的情況。當一個任務實例在TaskTracker節點的JVM中執行時除了成功執行意外,還有可能出現一些異常情況:1).在JVM中執行失敗;2).JVM進程被操作系統stop;3).任務實例被JobTracker節點要求kill;這些異常情況都會造成該任務實例執行的失敗,從而使得該任務進入FAILED、FAILED_UNCLEAN、KILLED_UNCLEAN等三種狀態中的某一種。這裏就有一個問題了,一個任務實例失敗時到底會進入哪一種狀態?這其實很好判斷:
1).如果一個任務實例在JVM中運行時出現異常或錯誤而無法再繼續運行,同時在調用了該任務所屬作業對應的OutputCimmitter輸出提交器的abortTask()方法之後離開JVM的話,這個任務實例會進入FAILED狀態;
2).如果一個任務實例在JVM中運行時出現異常或錯誤而無法再繼續運行,同時在沒有調用該任務所屬作業對應的OutputCimmitter輸出提交器的abortTask()方法就離開了JVM的話,這個任務實例會進入FAILED_UNCLEAN狀態;
3).如果一個任務實例在JVM中正常運行時突然被停止了(如:任務實例所在的JVM進程被OSstop或者被TaskTracker節點強制命令停止),此時還來不起調用該任務所屬作業對應的輸出提交器OutputCimmitter的abortTask()方法,所以它會進入KILLED_UNCLEAN狀態。
本文將主要圍繞JobTracker節點對處於FAILED_UNCLEAN和KILLED_UNCLEAN狀態的任務實例的處理來詳細地展開講解。
TaskTracker在任務實例停止執行之後,就會把這個任務實例對應的狀態報告給JobTracker節點來處理,當然,前面說過,JobTracker節點是不會直接處理任何任務實例的狀態報告的,而是交給對應的JobInProgress來處理。對於處於FAILED_UNCLEAN和KILLED_UNCLEAN狀態的任務實例,JobInProgress會將他們存儲在對應的待清理的任務隊列中,當然,一個作業主要包含兩種這樣的任務隊列,一種存儲Map型的任務實例,另一種存儲Reduce型的任務實例,然後它會交給合適的TaskTracker節點來執行對該任務的清理操作。這種清理工作就是前面所說的TaskCleanup任務。這個處理過程是是很簡單的,對應的源代碼如下:
class JobInProgress {
...
public synchronized void updateTaskStatus(TaskInProgress tip, TaskStatus status) {
...
if (state == TaskStatus.State.FAILED_UNCLEAN || state == TaskStatus.State.KILLED_UNCLEAN) {
tip.incompleteSubTask(taskid, this.status);
// add this task, to be rescheduled as cleanup attempt
if (tip.isMapTask()) {
mapCleanupTasks.add(taskid);
} else {
reduceCleanupTasks.add(taskid);
}
// Remove the task entry from jobtracker
jobtracker.removeTaskEntry(taskid);
}
...
}
...
}
上一篇博文也說過,當一個作業中有TaskCleanup任務的話,就會優先調度這些TaskCleanup任務,而不會調度它的正式Map/Reduce任務。對應的調度策略也很簡單,源碼如下:
public Task obtainTaskCleanupTask(TaskTrackerStatus tts, boolean isMapSlot) throws IOException {
if (!tasksInited.get()) {
return null;
}
synchronized (this) {
if (this.status.getRunState() != JobStatus.RUNNING || jobFailed || jobKilled) {
return null;
}
String taskTracker = tts.getTrackerName();
if (!shouldRunOnTaskTracker(taskTracker)) {
return null;
}
TaskAttemptID taskid = null;
TaskInProgress tip = null;
if (isMapSlot) {
if (!mapCleanupTasks.isEmpty()) {
taskid = mapCleanupTasks.remove(0);
tip = maps[taskid.getTaskID().getId()];
}
} else {
if (!reduceCleanupTasks.isEmpty()) {
taskid = reduceCleanupTasks.remove(0);
tip = reduces[taskid.getTaskID().getId()];
}
}
if (tip != null) {
return tip.addRunningTask(taskid, taskTracker, true);
}
return null;
}
}
TaskTracker節點對TaskCleanup任務的本地化和調度同JobSetup、JobCleanup、Map、Reduce任務是一樣,最終都會交給一個JVM實例來負責執行。在JVM中,它主要會調用作業對應的輸出提交器OutputCimmitter的abortTask()方法,即放棄該任務,在FileOutputCimmitter實現中就是清理該任務實例在執行過程中所佔用的臨時存儲空間。這裏要提醒的是,無論這個TaskCleanup任務在JVM中執行成功或者失敗,或者在本地化時就出錯而被kill掉,它都會進入對應的FAILED或者KILLED狀態:如果該TaskCleanup任務處於FAILED_UNCLEAN狀態,它就會進入FAILED狀態;和如果該TaskCleanup處於KILLED_UNCLEAN狀態,它就會進入KILLED狀態。TaskCleanup任務被TaskTracker節點執行完之後的處理同JobSetup、JobCleanup、Map、Reduce任務實例也是一樣的,所以就不再贅述了。