Job的任務執行流程之JobSetup階段

      客戶端用戶在成功提交Job之後,該Job並不會馬上被JobTracker節點調度執行,因爲這個Job還沒有被初始化,至於JobTracker節點是如何初始化Job的,我在前面已經詳細的談到過。當一個提交的Job在被初始化之後,就可以正式地被JobTracker節點調度執行了,不過,JobTracker節點不會就此開始調度Job的Map/Reduce任務,而是調用該Job的SetupTask。因此,本文將重點講解一個Job執行的Setup階段,在這個過程中我主要會以Job及其Task的狀態轉移爲主線來詳細的展開。

    Job作業被用戶成功提交之後就處於PREP狀態但此時JobTracker節點並不會就此馬上開始調度這個作業的Map/Reduce任務,而是先調度這個Job的SetupTask任務(請注意這個Task的類型是Map),很明顯JobTracker會爲這個TaskInProgress創建一個任務實例MapTask,此時這個MapTask的狀態是UNASSIGNED,之後JobTracker節點就會將這個MapTask交給一個合適的TaskTracker節點來執行。這個TaskTracker節點收到這個MapTask實例之後,就會爲被包裝成一個本地TaskInProgress,該TaskInProgress的狀態和MapTask實例的狀態一樣是UNASSIGNED。之後這個本地TaskInProgress就會等待TaskTracker節點上的一個空閒的Map Slot來執行。當這個本地TaskInProgressTaskTracker節點調度並本地初始化之後,這個TaskInProgress的狀態就變成了RUNNING。不過,在MapTask到達TaskTracker節點之後,TaskTracker節點會通過心跳包不斷地向JobTracker節點彙報這個MapTask對應的狀態信息。


        Map/Reduce任務在同一時刻可能有多個對應的實例在不同的TaskTracker節點上運行,但是Job的SetupTask任務在同一時刻只能有一個實例在TaskTracker節點上運行,也就是說如果一個Job的SetupTask任務實例被分配到某一個TaskTracker節點上運行的話,那麼JobTracker節點就不能再爲這個Job的SetupTask任務創建一個實例並交給其它的TaskTracker節點運行,除非JobTracker節點已經明確地知道了那個SetupTask任務實例已經執行失敗了。如果SetupTask的任務實例被成功執行之後JobInProgress的狀態就變爲RUNNING。TaskTracker節點在向JobTracker節點報告其上的所有任務實例的運行時狀態之後,就會清楚那些處於SUCCEEDEDFAILDKILLED狀態的任務實例,即它不會再向JobTracker節點報告這些已經結束的任務實例。JobTracker節點在收到一個任務實例的狀態報告之後會更新這個任務實例所屬的任務TaskInProgress的狀態信息,這個狀態更新處理的源代碼如下:

//返回的結果是這個任務的狀態有沒有發生變化
synchronized boolean updateStatus(TaskStatus status) {
    TaskAttemptID taskid = status.getTaskID();
    String diagInfo = status.getDiagnosticInfo();
    TaskStatus oldStatus = taskStatuses.get(taskid);//這個任務實例上一次的狀態
    boolean changed = true;
    
    if (diagInfo != null && diagInfo.length() > 0) {
      LOG.info("Error from "+taskid+": "+diagInfo);
      addDiagnosticInfo(taskid, diagInfo);
    }
    
    if(skipping) {
      failedRanges.updateState(status);
    }
    
    if (oldStatus != null) {
      TaskStatus.State oldState = oldStatus.getRunState();
      TaskStatus.State newState = status.getRunState();
          
      // 已經完成的任務實例(success/failure/killed)不可能被TaskTracker節點報告兩次,
      // 但爲了安全起見還是先檢查一下
      if ((newState != TaskStatus.State.RUNNING && newState != TaskStatus.State.COMMIT_PENDING && newState != TaskStatus.State.FAILED_UNCLEAN &&  newState != TaskStatus.State.KILLED_UNCLEAN &&  newState != TaskStatus.State.UNASSIGNED) && (oldState == newState)) {
        LOG.warn("Recieved duplicate status update of '" + newState +  "' for '" + taskid + "' of TIP '" + getTIPId() + "'");
        return false;
      }

      // 任務實例的狀態不肯能往回轉移,但爲了安全起見還是要檢查一下.
      if ((newState == TaskStatus.State.RUNNING || 
          newState == TaskStatus.State.UNASSIGNED) &&
          (oldState == TaskStatus.State.FAILED || 
           oldState == TaskStatus.State.KILLED || 
           oldState == TaskStatus.State.FAILED_UNCLEAN || 
           oldState == TaskStatus.State.KILLED_UNCLEAN || 
           oldState == TaskStatus.State.SUCCEEDED ||
           oldState == TaskStatus.State.COMMIT_PENDING)) {
        return false;
      }
      
      //任務的實例狀態已經是FAILED/KILLED,就不會再更新了
      if (oldState == TaskStatus.State.FAILED || oldState == TaskStatus.State.KILLED) {
        tasksToKill.put(taskid, true);
        return false;	  
      }
          
      changed = oldState != newState;
    }

    // 更新任務實例的狀態.
    if (!isCleanupAttempt(taskid)) {
      taskStatuses.put(taskid, status);
    } else {
      taskStatuses.get(taskid).statusUpdate(status.getRunState(), status.getProgress(), status.getStateString(), status.getPhase(), status.getFinishTime());
    }

    //重新計算任務的當前進度
    recomputeProgress();
    return changed;
  }

      JobTracker節點並沒有限制一個作業的最大執行時間,這就是說,如果一個Job的一個Task在有限的時間內無法執行完或者說無法完成的話,JobTracker節點並沒有相應的機制來自動的kill掉這個作業,這個作業將會一直的跑下去,知道提交者自己手動強制結束掉這個任務。另外,Job的SetupTask任務的主要任務就是爲該Job做一些準備工作,如申請最後的輸出數據的存儲空間等,它的實現就是調用OutputCommitter的setupJob()方法,這個OutputCommitter來源於該Job配置的輸出格式化器OutputFormat,不過在此之前,它還必須調用OutputCommitter的setupTask()方法,這是因爲任何一個Task實例在任何一個JVM中執行真正的任務之前都必須調用OutputCommittersetupTask()方法來啓動任務,儘管有些OutputCommitter的實現中setupTask()方法沒有做任何事情,但這個方法可以看做是Task執行過程中的一個鉤子方法以便解決用戶的特殊問題。值得一提的是,目前的Map-Reduce執行框架對於一個JobSetup任務實例,並不保證在這個任務實例執行失敗時,一定會調用對應的OutputCommitter的abortTask()方法,所以,如果用戶想自己實現一個特定作業的OutputCommitter的時候,一定要自己來保證setupTask()方法的事務一致性。同時,這也告訴我們,對於一般的Map/Reduce任務,我們可以通過OutputCommittersetupTask()和abortTask()來保證任務實例執行時的一致性(主要是原子性)。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章