前面談到過,每一個TaskTracker節點都要在它向JobTracker節點發送心跳包的時候順帶報告運行在其上的Task的狀態信息,這些Task是指正在TaskTracker節點上運行的,或者從上一次報告到現在的時間間隔中完成的或者是失敗的任務。JobTracker節點之所以需要收集這些正在執行或者剛完成的任務的狀態信息,是因爲它要及時掌握各個作業的執行進度,一方面將此報告給用戶,一方面還調整作業的任務調度,如任務的意外處理,任務/作業的善後處理等。這裏說的任務的意外處理包括,對於失敗的任務或者是拖後退的任務需要從新安排其它的TaskTracker節點來執行等,詳細情況我會在後面再作介紹。
對於JobTracker節點處理TaskTracker發送過來的Task狀態報告的詳細過程,我將結合源代碼來闡述。但在此之前,我想先介紹一下相關的幾個概念:作業/作業實例、任務/任務實例。作業指的是用戶提交的作業,用Job的實例對象來表示,作業實例指的是Job運行的狀態信息,用JobInProgress的實例對象來表示;任務指的是該任務的執行進度狀態信息,用TaskInProgress的實例對象來表示,任務實例則指的是在將該任務交由某一個TaskTacker節點執行的任務,用TaskAttempID的實例對象來表示。一個作業對應一個作業實例,而一個任務可能對應多個任務實例,這是因爲一個任務可能交給了多個TaskTracker節點來執行。JobTracker節點處理Task實例狀態報告的源碼如下:
void updateTaskStatuses(TaskTrackerStatus status) {
String trackerName = status.getTrackerName();
for (TaskStatus report : status.getTaskReports()) {
report.setTaskTracker(trackerName);
TaskAttemptID taskId = report.getTaskID();
// expire it
expireLaunchingTasks.removeTask(taskId);
JobInProgress job = getJob(taskId.getJobID());
if (job == null) {
// if job is not there in the cleanup list ... add it
synchronized (trackerToJobsToCleanup) {
Set<JobID> jobs = trackerToJobsToCleanup.get(trackerName);
if (jobs == null) {
jobs = new HashSet<JobID>();
trackerToJobsToCleanup.put(trackerName, jobs);
}
jobs.add(taskId.getJobID());
}
continue;
}
if (!job.inited()) {
// if job is not yet initialized ... kill the attempt
synchronized (trackerToTasksToCleanup) {
Set<TaskAttemptID> tasks = trackerToTasksToCleanup.get(trackerName);
if (tasks == null) {
tasks = new HashSet<TaskAttemptID>();
trackerToTasksToCleanup.put(trackerName, tasks);
}
tasks.add(taskId);
}
continue;
}
TaskInProgress tip = taskidToTIPMap.get(taskId);
// Check if the tip is known to the jobtracker. In case of a restarted
// jt, some tasks might join in later
if (tip != null || hasRestarted()) {
if (tip == null) {
tip = job.getTaskInProgress(taskId.getTaskID());
job.addRunningTaskToTIP(tip, taskId, status, false);
}
// Update the job and inform the listeners if necessary
JobStatus prevStatus = (JobStatus)job.getStatus().clone();
// Clone TaskStatus object here, because JobInProgress
// or TaskInProgress can modify this object and
// the changes should not get reflected in TaskTrackerStatus.
// An old TaskTrackerStatus is used later in countMapTasks, etc.
job.updateTaskStatus(tip, (TaskStatus)report.clone());
JobStatus newStatus = (JobStatus)job.getStatus().clone();
// Update the listeners if an incomplete job completes
if (prevStatus.getRunState() != newStatus.getRunState()) {
JobStatusChangeEvent event = new JobStatusChangeEvent(job, EventType.RUN_STATE_CHANGED, prevStatus, newStatus);
updateJobInProgressListeners(event);
}
} else {
LOG.info("Serious problem. While updating status, cannot find taskid " + report.getTaskID());
}
// Process 'failed fetch' notifications
List<TaskAttemptID> failedFetchMaps = report.getFetchFailedMaps();
if (failedFetchMaps != null) {
for (TaskAttemptID mapTaskId : failedFetchMaps) {
TaskInProgress failedFetchMap = taskidToTIPMap.get(mapTaskId);
if (failedFetchMap != null) {
// Gather information about the map which has to be failed, if need be
String failedFetchTrackerName = getAssignedTracker(mapTaskId);
if (failedFetchTrackerName == null) {
failedFetchTrackerName = "Lost task tracker";
}
failedFetchMap.getJob().fetchFailureNotification(failedFetchMap, mapTaskId, failedFetchTrackerName);
}
}
}
}
}
從上面的源代碼可以看出,JobTracker節點對於每一個Task狀態報告的處理考慮到了一些意外情況,這些意外情況主要包括:
1).Task實例所屬的作業不在JobTracker節點的任務隊列內。出現這個情況的可能原因是這個Task實例是一個拖後退的任務,當它完成時,該Task實例所屬的Job早已被完成了,或者該Job被用戶kill掉了。對於這種情況,JobTracker節點就直接讓該TaskTracker把運行在其上的所有屬於該Job的Task全部清除(這個TaskTracker節點上可能還運行有該Job的拖後腿任務)。
2).Task實例所屬的作業還沒有初始化(以前說過,沒有被初始化的Job是不能被JobTracker節點調度執行的)。出現這種情況的一個可能的原因是TaskTracker節點剛剛重啓過,在恢復作業的時候還沒有來得及初始化該Job。對於這種情況,JobTracker節點就直接讓該TaskTracker清除該任務。
3).Task實例所屬的任務還沒有被JobTracker節點調度執行。出現這種情況的一個可能的原因是JobTracker節點在該Task實例執行期間重啓了,重啓之後還沒有來得及調度該任務。對於這種情況,JobTracker節點爲了提高性能就認爲該情況是還算是正常的,不過得先得通知該Task實例所屬的Job實例。
4).對於Reduce Task實例的進度狀態報告,它還會附帶額外的報告信息,就是報告那些它無法fetch到map輸出的Map任務,JobTracker節點需要將這些消息轉告給該Task實例所屬的作業實例,以便該Job實例能夠及時調整其內部任務的調度。至於Job實例是如何處理的,並不是本文所要介紹的重點,不過我會在以後的博文中詳細闡述。
在正常的情況下,JobTracker節點不會直接的處理這些Task實例的狀態報告,而是將其交給它們所屬的Job實例來處理,Job實例處理之後如果它的狀態發生變化,JobTracker節點會通知對應的事件監聽器。那麼,Job實例又是如何根據它的一個Task實例狀態來更新自己當前的狀態呢?由於這個過程相當的複雜不適合於此時介紹。