Hadoop源碼解讀-Job初始化過程

首先看看Hadoop ssh 腳本 
   
elif [ "$COMMAND" = "jar" ] ; then 
      CLASS=org.apache.hadoop.util.RunJar 

任務遞交。 
WordCount 裏面有一句話: 
   
Java代碼  收藏代碼
  1. System.exit(job.waitForCompletion(true) ? 0 : 1);  

  

1.job.waitForCompletion:一般情況下我們提交一個job都是通過job.waitForCompletion方法提交,該方法內部會調用job.submit()方法 
Java代碼  收藏代碼
  1. public boolean waitForCompletion(boolean verbose    
  2.                                    ) throws IOException, InterruptedException,    
  3.                                             ClassNotFoundException {    
  4.     if (state == JobState.DEFINE) {    
  5.       submit();    
  6.     }    
  7.     if (verbose) {    
  8.       jobClient.monitorAndPrintJob(conf, info);    
  9.     } else {    
  10.       info.waitForCompletion();    
  11.     }    
  12.     return isSuccessful();    
  13.   }    


2.job.submit():在submit中會調用setUseNewAPI(),setUseNewAPI()這個方法主要是判斷是使用新的api還是舊的api,之後會調用connect()方法,該方法主要是實例化jobClient,然後會調用jobClient.submitJobInternal(conf)這個方法進行job的提交 
Java代碼  收藏代碼
  1. public void submit() throws IOException, InterruptedException,     
  2.                               ClassNotFoundException {    
  3.     ensureState(JobState.DEFINE);    
  4.     setUseNewAPI();    
  5.         
  6.     // Connect to the JobTracker and submit the job    
  7.     connect();    
  8.     info = jobClient.submitJobInternal(conf);    
  9.     super.setJobID(info.getID());    
  10.     state = JobState.RUNNING;    
  11.    }    


3.jobClient.submitJobInternal():這個方法會將job運行時所需的所有文件上傳到jobTarcker文件系統(一般是hdfs)中,同時進行備份(備份數默認是10,通過mapred.submit.replication變量可以設置),這個方法需要深入進行解讀。 
4.JobSubmissionFiles.getStagingDir:這個方法是在jobClient.submitJobInternal()最先調用的,這個方法主要是獲取一個job提交的根目錄,主要是通過Path stagingArea = client.getStagingAreaDir();方法獲得,這個方法最終會調用jobTracker.getStagingAreaDirInternal()方法,代碼如下: 
Java代碼  收藏代碼
  1. private String getStagingAreaDirInternal(String user) throws IOException {    
  2.    final Path stagingRootDir =    
  3.      new Path(conf.get("mapreduce.jobtracker.staging.root.dir",    
  4.            "/tmp/hadoop/mapred/staging"));    
  5.    final FileSystem fs = stagingRootDir.getFileSystem(conf);    
  6.    return fs.makeQualified(new Path(stagingRootDir,    
  7.                              user+"/.staging")).toString();    
  8.  }    


在獲取了stagingDir之後會執行JobID jobId = jobSubmitClient.getNewJobId();爲job獲取一個jobId,然後執行Path submitJobDir = new Path(jobStagingArea, jobId.toString());獲得該job提交的路徑,也就是在stagingDir目錄下建一個以jobId爲文件名的目錄。有了submitJobDir之後就可以將job運行所需的全部文件上傳到對應的目錄下了,具體是調用jobClient.copyAndConfigureFiles(jobCopy, submitJobDir)這個方法。 

5.jobClient.copyAndConfigureFiles(jobCopy, submitJobDir):這個方法最終調用jobClient.copyAndConfigureFiles(job, jobSubmitDir, replication);這個方法實現文件上傳。 

6.jobClient.copyAndConfigureFiles(job, jobSubmitDir, replication):這個方法首先獲取用戶在使用命令執行job的時候所指定的-libjars, -files, -archives文件,對應的conf配置參數是tmpfiles tmpjars tmparchives,這個過程是在ToolRunner.run()的時候進行解析的,當用戶指定了這三個參數之後,會將這三個參數對應的文件都上傳到hdfs上,下面我們具體看一個參數的處理:tmpfiles(其他兩個基本相同) 

7.jobClient處理tmpfiles:該方法會將tmpfiles參數值按‘,’分割,然後將每一個文件上傳到hdfs,其中如何文件的路徑本身就在hdfs中,那麼將不進行上傳操作,上傳操作只針對文件不在hdfs中的文件。調用的方法是: 
Path newPath = copyRemoteFiles(fs,filesDir, tmp, job, replication), 
該方法內部使用的是FileUtil.copy(remoteFs, originalPath, jtFs, newPath, false, job)方法將文件上傳至hdfs,注意此處的remoteFs和jtFs,remoteFs就是需上傳文件的原始文件系統,jtFs則是jobTracker的文件系統(hdfs)。 
在文件上傳至hdfs之後,會執行DistributedCache.createSymlink(job)這個方法,這個方法是創建一個別名(好像是這麼個名字),這裏需要注意的是tmpfiles和tmparchives都會創建別名,而tmpjars則不會,個人認爲tmpjars則jar文件,不是用戶在job運行期間調用,所以不需要別名,而tmpfiles和tmparchives則在job運行期間用戶可能會調用,所以使用別名可以方便用戶調用 

8.將這三個參數指定的文件上傳到hdfs之後,需要將job的jar文件上傳到hdfs,名稱爲submitJobDir/job.jar,使用fs.copyFromLocalFile(originalJarFile, submitJarFile)上傳即可。 
到這裏jobClient.copyAndConfigureFiles(jobCopy, submitJobDir)方法就完成了,期間丟了jobClient.copyAndConfigureFiles(jobCopy, submitJobDir),TrackerDistributedCacheManager.determineTimestampsAndCacheVisibilities(job),TrackerDistributedCacheManager.getDelegationTokens(job,  job.getCredentials()) 
三個方法,這三個方法是進行一些cached archives and files的校驗和保存其時間戳和權限內容 

9.繼續我們的jobClient.submitJobInternal()方法,這之後會根據我們設置的outputFormat類執行output.checkOutputSpecs(context),進行輸出路徑的檢驗,主要是保證輸出路徑不存在,存在會拋出異常。這之後就是對輸入文件進行分片操作了,writeSplits(context, submitJobDir)。 
10.jobClient.writeSplits():這個方法內部會根據我們之前判斷的使用new-api還是old-api分別進行分片操作,我們只看new-api的分片操作。 
Java代碼  收藏代碼
  1.    
  2. private int writeSplits(org.apache.hadoop.mapreduce.JobContext job,    
  3.       Path jobSubmitDir) throws IOException,    
  4.       InterruptedException, ClassNotFoundException {    
  5.     JobConf jConf = (JobConf)job.getConfiguration();    
  6.     int maps;    
  7.     if (jConf.getUseNewMapper()) {    
  8.       maps = writeNewSplits(job, jobSubmitDir);    
  9.     } else {    
  10.       maps = writeOldSplits(jConf, jobSubmitDir);    
  11.     }    
  12.     return maps;    
  13.   }   
  14.    

11.jobClient.writeNewSplits():這個方法主要是根據我們設置的inputFormat.class通過反射獲得inputFormat對象,然後調用inputFormat對象的getSplits方法,當獲得分片信息之後調用JobSplitWriter.createSplitFiles方法將分片的信息寫入到submitJobDir/job.split文件中。 
Java代碼  收藏代碼
  1. private <T extends InputSplit>    
  2.  int writeNewSplits(JobContext job, Path jobSubmitDir) throws IOException,    
  3.      InterruptedException, ClassNotFoundException {    
  4.    Configuration conf = job.getConfiguration();    
  5.    InputFormat<?, ?> input =    
  6.      ReflectionUtils.newInstance(job.getInputFormatClass(), conf);    
  7.     
  8.    List<InputSplit> splits = input.getSplits(job);    
  9.    T[] array = (T[]) splits.toArray(new InputSplit[splits.size()]);    
  10.     
  11.    // sort the splits into order based on size, so that the biggest    
  12.    // go first    
  13.    Arrays.sort(array, new SplitComparator());    
  14.    JobSplitWriter.createSplitFiles(jobSubmitDir, conf,    
  15.        jobSubmitDir.getFileSystem(conf), array);    
  16.    return array.length;    
  17.  }    


12.JobSplitWriter.createSplitFiles:這個方法的作用就是講分片信息寫入到submitJobDir/job.split文件中,方法內部調用 
JobSplitWriter.writeNewSplits進行寫操作 

13.JobSplitWriter.writeNewSplits:該方法具體對每一個InputSplit對象進行序列化寫入到輸出流中,具體每個InputSplit對象寫入的信息包括: 
split.getClass().getName(),serializer.serialize(split)將整個對象序列化。然後將InputSplit對象的locations信息放入SplitMetaInfo對象中,同時還包括InputSpilt元信息在job.split文件中的偏移量,該InputSplit的長度,再將SplitMetaInfo對象。 
然後調用JobSplitWriter.writeJobSplitMetaInfo()方法將SplitMetaInfo對象寫入submitJobDir/job.splitmetainfo文件中。 

14.JobSplitWriter.writeJobSplitMetaInfo(): 
將SplitMetaInfo對象寫入submitJobDir/job.splitmetainfo文件中, 
具體寫入的信息包括:JobSplit.META_SPLIT_FILE_HEADER,splitVersion,allSplitMetaInfo.length(SplitMetaInfo對象的個數,一個split對應一個SplitMetaInfo),然後分別將所有的SplitMetaInfo對象序列化到輸出流中, 
到此文件的分片工作完成。 

15.繼續回頭看jobClient.submitJobInternal()方法:在上一步進行分片操作之後,或返回切片的數目,據此設定map的數量,所以在job中設置的map數量是沒有用的。 
16.繼續往下走: 
Java代碼  收藏代碼
  1. String queue = jobCopy.getQueueName();    
  2.           AccessControlList acl = jobSubmitClient.getQueueAdmins(queue);    
  3.           jobCopy.set(QueueManager.toFullPropertyName(queue,    
  4.               QueueACL.ADMINISTER_JOBS.getAclName()), acl.getACLString());   

這三句話是獲得job對應的任務隊列信息,這裏涉及到hadoop的作業調度內容,就不深入研究了 

17.繼續:下面就是講job的配置文件信息(jobConf對象)寫入到xml文件中,以便用戶查看,具體文件是:submitJobDir/job.xml,通過jobCopy.writeXml(out)方法, 
方法比較簡單,就是寫xml文件。下面就進入到jobTracker提交任務環節了,status = jobSubmitClient.submitJob(jobId, submitJobDir.toString(), jobCopy.getCredentials()), 
就到這吧,後面下次再慢慢研究。 
總結下:在用戶提交job之後,第一步主要是jobClient對job進行一些必要的文件上傳操作 
,主要包括: 

1)爲job生成一個jobId,然後獲得job提交的stagingDir,根據jobId獲得submitJobDir,之後所有的job運行時文件豆漿保存在此目錄下 

2)將用戶在命令行通過-libjars, -files, -archives指定的文件上傳到jobTracker的文件系統中,並將job.jar上傳到hdfs中 

3)校驗輸出路徑 

4)進行輸入文件的分片操作,並將分片信息寫入submitJobDir下的相應文件中,有兩個文件:job.split以及job.splitmetainfo 

5)將job的配置參數(jobConf對象)寫入到job.xml文件中。 

18. jobTracker進行job的提交過程,還有一個JobSubmissionProtocol的實現是LocalJobRunner,這是本地執行的時候使用的,真正集羣運行Job還是使用的jobTracker,所以只看jobTracker類的submitJob 
      18.1.jobTracker.submitJob():第一句就是checkJobTrackerState()這個是檢查jobTracker狀態,是否運行中,這裏說一句,jobTracker是在hadoop集羣啓動的時候啓動的,也就是在執行start-all或者start-mapred的時候啓動,啓動的時候會調用JobTracker的main方法,然後在jps的時候就可以看見一個jobTracker的進程了。下面來看一下JobTracker.main()方法。 
       18.2.JobTracker.main():第一句是JobTracker tracker = startTracker(new JobConf()),這是實例化一個jobTracke實例。 
       18.3.JobTracker.startTracker():result = new JobTracker(conf, identifier),實例化一個jobTracker對象,在實例化的時候會做很多事,所以還是進去瞅瞅。 
       18.4.JobTracker.JobTracker():實例化的時候會初始化很多參數,記也記不住,主要看下實例化taskScheduler的內容: 
Java代碼  收藏代碼
  1. Class<? extends TaskScheduler> schedulerClass  
  2.       = conf.getClass("mapred.jobtracker.taskScheduler",JobQueueTaskScheduler.class, TaskScheduler.class);  taskScheduler = (TaskScheduler) ReflectionUtils.newInstance(schedulerClass, conf),  

這兩句就是根據配置文件設置的taskScheduler類名, 
通過反射獲得對應的taskScheduler對象, 
在實例化的時候雖然不同的TaskScheduler具體操作不一樣,但是統一的都會初始化一個JobListener對象, 
這個對象就是後面將要監聽job的listener。剩下的內容就不說了。 
回到JobTracker.startTracker()方法。 
5.JobTracker.JobTracker(): 
在實例化jobTracker之後, 
會執行result.taskScheduler.setTaskTrackerManager(result) 
,這個就是將jobTracker對象設置給taskScheduler。後面就什麼了,現在可以回到main方法了 
Java代碼  收藏代碼
  1. public static JobTracker startTracker(JobConf conf, String identifier, boolean initialize)     
  2.   throws IOException, InterruptedException {    
  3.     DefaultMetricsSystem.initialize("JobTracker");    
  4.     JobTracker result = null;    
  5.     while (true) {    
  6.       try {    
  7.         result = new JobTracker(conf, identifier);    
  8.         result.taskScheduler.setTaskTrackerManager(result);    
  9.         break;    
  10.       } catch (VersionMismatch e) {    
  11.         throw e;    
  12.       } catch (BindException e) {    
  13.         throw e;    
  14.       } catch (UnknownHostException e) {    
  15.         throw e;    
  16.       } catch (AccessControlException ace) {    
  17.         // in case of jobtracker not having right access    
  18.         // bail out    
  19.         throw ace;    
  20.       } catch (IOException e) {    
  21.         LOG.warn("Error starting tracker: " +     
  22.                  StringUtils.stringifyException(e));    
  23.       }    
  24.       Thread.sleep(1000);    
  25.     }    
  26.     if (result != null) {    
  27.       JobEndNotifier.startNotifier();    
  28.       MBeans.register("JobTracker""JobTrackerInfo", result);    
  29.       if(initialize == true) {    
  30.         result.setSafeModeInternal(SafeModeAction.SAFEMODE_ENTER);    
  31.         result.initializeFilesystem();    
  32.         result.setSafeModeInternal(SafeModeAction.SAFEMODE_LEAVE);    
  33.         result.initialize();    
  34.       }    
  35.     }    
  36.     return result;    
  37.   }    


.JobTracker.main():在實例化jobTracker之後,會調用tracker.offerService()方法, 
之後main方法就沒什麼了,下面看看tracker.offerService()這個方法 
Java代碼  收藏代碼
  1. public static void main(String argv[]    
  2.                           ) throws IOException, InterruptedException {    
  3.     StringUtils.startupShutdownMessage(JobTracker.class, argv, LOG);    
  4.         
  5.     try {    
  6.       if(argv.length == 0) {    
  7.         JobTracker tracker = startTracker(new JobConf());    
  8.         tracker.offerService();    
  9.       }    
  10.       else {    
  11.         if ("-dumpConfiguration".equals(argv[0]) && argv.length == 1) {    
  12.           dumpConfiguration(new PrintWriter(System.out));    
  13.         }    
  14.         else {    
  15.           System.out.println("usage: JobTracker [-dumpConfiguration]");    
  16.           System.exit(-1);    
  17.         }    
  18.       }    
  19.     } catch (Throwable e) {    
  20.       LOG.fatal(StringUtils.stringifyException(e));    
  21.       System.exit(-1);    
  22.     }    
  23.   }    


只看taskScheduler.start()這個方法,因爲這裏只是想分析下JobTracker提交job的過程. 
    18.5 taskScheduler.start():這個方法就是啓動TaskScheduler,這個方法不同taskScheduler也不同,但是統一的還是會有一個taskTrackerManager.addJobInProgressListener(jobListener)這個操作,taskTrackerManager就是jobTracker(第5步),這句的意思是爲jobTracker添加jobListener,用來監聽job的。這句的內部就是調用jobTracker的jobInProgressListeners集合的add(listener)方法。 
到這裏可以說看完了整個JobTracker的啓動過程,雖然很淺顯,但是對於後面將要分析的內容,這些就夠了。下面來看看job的提交過程,也就是jobTracker的submit()方法。 

      jobTracker的submit() 
一, 第一步是checkSafeMode(),檢查是否在安全模式,在安全模式則拋出異常。 
然後執行 
jobInfo = new JobInfo(jobId, new Text(ugi.getShortUserName()),new Path(jobSubmitDir),生成一個jobInfo對象,jobInfo主要保存job的id,user,jobSubmitDir(也就是job的任務目錄,上一篇文章提到)。 
接着是判斷job是否可被recovered(job失敗的時候嘗試再次執行), 
如果允許的話(默認允許),則將jobInfo對象序列化到job-info文件中。 
接着到達最關鍵的地方,job = new JobInProgress(this, this.conf, jobInfo, 0, ts), 
爲job實例化一個JobInProgress對象,這個對象將會對job以後的所有情況進行負責,如初始化,執行等。下面看看JobInProgress對象的初始化操作。 
  二, 這裏看下將job.xml下載到本地的操作。然後就是job的隊列信息, 
默認的隊列名是default,Queue queue = this.jobtracker.getQueueManager().getQueue(queueName),這個主要是根據hadoop所使用的taskScheduler有關,具體不研究。剩下的是一些參數的初始化,如map的數目,reduce的數目等。這裏還有個設置job的優先級的,默認是normal。this.priority = conf.getJobPriority();this.status.setJobPriority(this.priority); 
還有檢查taskLimit的操作,就是檢查map+reduce的任務數是否超出mapred.jobtracker.maxtasks.per.job設置的值,默認是-1,就是沒有限制的意思。回到jobTracker.submit()方法 

Java代碼  收藏代碼
  1. this.localJobFile = default_conf.getLocalPath(JobTracker.SUBDIR    
  2.           +"/"+jobId + ".xml");    
  3.       Path jobFilePath = JobSubmissionFiles.getJobConfPath(jobSubmitDir);    
  4.       jobFile = jobFilePath.toString();    
  5.       fs.copyToLocalFile(jobFilePath, localJobFile);    
  6.       conf = new JobConf(localJobFile);    

   三, submit():實例化JobInProgress之後,會根據jobProfile獲取job的隊列信息, 
並判斷相應的隊列是否在運行中,不在則任務失敗。然後檢查內存情況checkMemoryRequirements(job),再調用taskScheduler的taskScheduler.checkJobSubmission(job) 
檢查任務提交情況(具體是啥玩意,不太情況)。 
接下來就是執行 
status = addJob(jobId, job), 
爲Job設置listener。 
addJob():前面說過,在初始化jobTracker的時候會實例化taskScheduler, 
然後調用taskScheduler的start()方法,爲jobTracker添加JobListener對象, 
所以這裏的JobInProgressListener對象就是相應的taskScheduler的JobListener, 
這裏爲job添加了JobListener。 
Java代碼  收藏代碼
  1. private synchronized JobStatus addJob(JobID jobId, JobInProgress job)     
  2.   throws IOException {    
  3.     totalSubmissions++;    
  4.     
  5.     synchronized (jobs) {    
  6.       synchronized (taskScheduler) {    
  7.         jobs.put(job.getProfile().getJobID(), job);    
  8.         for (JobInProgressListener listener : jobInProgressListeners) {    
  9.           listener.jobAdded(job);    
  10.         }    
  11.       }    
  12.     }    
  13.     myInstrumentation.submitJob(job.getJobConf(), jobId);    
  14.     job.getQueueMetrics().submitJob(job.getJobConf(), jobId);    
  15.     
  16.     LOG.info("Job " + jobId + " added successfully for user '"     
  17.              + job.getJobConf().getUser() + "' to queue '"     
  18.              + job.getJobConf().getQueueName() + "'");    
  19.     AuditLogger.logSuccess(job.getUser(),     
  20.         Operation.SUBMIT_JOB.name(), jobId.toString());    
  21.     return job.getStatus();    
  22.   }    

==================== JobTracker init job================= 
JobTracker.initJob():主要調用job.initTasks(),下面進入到JobInProgress.initTasks()。 

JobInProgress.initTasks():爲job對象設置優先級setPriority(this.priority), 
接着讀取分片信息文件獲取分片信息,SplitMetaInfoReader.readSplitMetaInfo()這個方就是jobInPorgress用來讀取分分片信息的,讀取過程與寫入過程相對應, 
具體還是較簡單的。讀取了分片信息之後,根據分片數量創建相應數量的mapTask(TaskInProgress對象),接下來會執行nonRunningMapCache = createCache(splits, maxLevel),這個方法是根據每個分片的location信息,
然後根據location的host判斷每個host上所有的job,並放入cache中。 
接着根據設置的reduce數量新建對應的reduceTask(TaskInProgress對象), 
並加入到nonRunningReduces隊列中, 
並根據mapred.reduce.slowstart.completed.maps(百分比,默認是5%)參數的值計算completedMapsForReduceSlowstart( 
分別對應map和reduce task。到此initTask完成, 
initTask完成JobTracker的initJob也就差不多完成了, 

Java代碼  收藏代碼
  1.   public synchronized void initTasks()   
  2.   throws IOException, KillInterruptedException, UnknownHostException {  
  3.      // log the job priority  
  4.     setPriority(this.priority);  
  5.    //........  
  6.   
  7.    numMapTasks = splits.length;  
  8. // 添加Map 和 reduce   
  9.  jobtracker.getInstrumentation().addWaitingMaps(getJobID(), numMapTasks);  
  10.     jobtracker.getInstrumentation().addWaitingReduces(getJobID(), numReduceTasks);  
  11.   //....  
  12. //create map;  
  13.    maps = new TaskInProgress[numMapTasks];  
  14. //....  
  15. //create reduce  
  16. this.reduces = new TaskInProgress[numReduceTasks];  
  17. //下面是cleanup 和log  
  18. //....  
  19.   
  20.   
  21. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章