mapreduce job提交流程原始碼級分析(三)中已經說明使用者最終呼叫JobTracker.submitJob方法來向JobTracker提交作業。而這個方法的核心提交方法是JobTracker.addJob(JobID jobId, JobInProgress job)方法,這個addJob方法會把Job提交到排程器(預設是JobQueueTaskScheduler)的監聽器JobQueueJobInProgressListener和EagerTaskInitializationListener(本文只討論預設排程器)中,使用方法jobAdded(JobInProgress job),JobQueueJobInProgressListener任務是監控各個JobInProcess生命週期中的變化;EagerTaskInitializationListener是發現有新Job後對其初始化的。
一、JobQueueJobInProgressListener.jobAdded(JobInProgress job)方法。就一句程式碼jobQueue.put(new JobSchedulingInfo(job.getStatus()), job),先構建一個JobSchedulingInfo物件,然後和JobInProgress對應起來放入jobQueue中。JobSchedulingInfo類維護這排程這個job必備的一些資訊,比如優先順序(預設是NORMAL)、JobID以及開始時間startTime。
二、EagerTaskInitializationListener.jobAdded(JobInProgress job)方法。
1 /** 2 * We add the JIP to the jobInitQueue, which is processed 3 * asynchronously to handle split-computation and build up 4 * the right TaskTracker/Block mapping. 5 */ 6 @Override 7 public void jobAdded(JobInProgress job) { 8 synchronized (jobInitQueue) { 9 jobInitQueue.add(job); //新增進List<JobInProgress> jobInitQueue 10 resortInitQueue(); 11 jobInitQueue.notifyAll(); //喚醒阻塞的程式 12 } 13 14 }
上面方法中resortInitQueue()方法主要是對jobInitQueue中JobInProcess進行排序,先按照優先順序排序,相同的再按開始時間。EagerTaskInitializationListener.start()在排程器初始化時JobQueueTaskScheduler.start()就呼叫了,所以先於jobAdded方法呼叫。EagerTaskInitializationListener.start()程式碼如下:
1 public void start() throws IOException { 2 this.jobInitManagerThread = new Thread(jobInitManager, "jobInitManager"); 3 jobInitManagerThread.setDaemon(true); 4 this.jobInitManagerThread.start(); 5 }
start()方法會啟動一個執行緒:JobInitManager。
1 ///////////////////////////////////////////////////////////////// 2 // Used to init new jobs that have just been created 3 ///////////////////////////////////////////////////////////////// 4 class JobInitManager implements Runnable { 5 6 public void run() { 7 JobInProgress job = null; 8 while (true) { 9 try { 10 synchronized (jobInitQueue) { 11 while (jobInitQueue.isEmpty()) { 12 jobInitQueue.wait(); 13 } 14 job = jobInitQueue.remove(0); 15 } 16 threadPool.execute(new InitJob(job)); 17 } catch (InterruptedException t) { 18 LOG.info("JobInitManagerThread interrupted."); 19 break; 20 } 21 } 22 LOG.info("Shutting down thread pool"); 23 threadPool.shutdownNow(); 24 } 25 } 26 27 class InitJob implements Runnable { 28 29 private JobInProgress job; 30 31 public InitJob(JobInProgress job) { 32 this.job = job; 33 } 34 35 public void run() { 36 ttm.initJob(job);//對應JobTracker的對應方法 37 } 38 }
JobInitManager執行緒的run方法是一個死迴圈始終監控jobInitQueue是否為空,不為空的話就取出0位置的JobInProgress,在InitJob執行緒中初始化:TaskTrackerManager.initJob(job)對應JobTracker的initJob方法。這裡為什麼會另起執行緒來初始化Job呢?原因很簡單,就是可能jobInitQueue中同時會有很多JobInProgress,一個一個的初始化會比較慢,所以採用多執行緒的方式初始化。來看initJob方法的程式碼:
1 public void initJob(JobInProgress job) { 2 if (null == job) { 3 LOG.info("Init on null job is not valid"); 4 return; 5 } 6 7 try { 8 JobStatus prevStatus = (JobStatus)job.getStatus().clone(); 9 LOG.info("Initializing " + job.getJobID()); 10 job.initTasks(); //呼叫該例項的initTasks方 法,對job進行初始化 11 // Inform the listeners if the job state has changed 12 // Note : that the job will be in PREP state. 13 JobStatus newStatus = (JobStatus)job.getStatus().clone(); 14 if (prevStatus.getRunState() != newStatus.getRunState()) { 15 JobStatusChangeEvent event = 16 new JobStatusChangeEvent(job, EventType.RUN_STATE_CHANGED, prevStatus, 17 newStatus); 18 synchronized (JobTracker.this) { 19 updateJobInProgressListeners(event); 20 } 21 } 22 } catch (KillInterruptedException kie) { 23 // If job was killed during initialization, job state will be KILLED 24 LOG.error("Job initialization interrupted:\n" + 25 StringUtils.stringifyException(kie)); 26 killJob(job); 27 } catch (Throwable t) { 28 String failureInfo = 29 "Job initialization failed:\n" + StringUtils.stringifyException(t); 30 // If the job initialization is failed, job state will be FAILED 31 LOG.error(failureInfo); 32 job.getStatus().setFailureInfo(failureInfo); 33 failJob(job); 34 } 35 }
首先是獲取初始化前的狀態prevStatus;然後是job.initTasks()初始化;在獲取初始化的後的狀態newStatus;
job.initTasks()方法程式碼比較多,主要的工作是檢查之後獲取輸入資料的分片資訊TaskSplitMetaInfo[] splits = createSplits(jobId)這是去讀的上傳到HDFS中的檔案job.splitmetainfo和job.split,要確保numMapTasks == splits.length,然後構建numMapTasks個TaskInProgress作為MapTask,