elif [ "$COMMAND" = "jar" ] ; then
CLASS=org.apache.hadoop.util.RunJar
任務遞交。
WordCount 裏面有一句話:
- System.exit(job.waitForCompletion(true) ? 0 : 1);
1.job.waitForCompletion:一般情況下我們提交一個job都是通過job.waitForCompletion方法提交,該方法內部會調用job.submit()方法
- public boolean waitForCompletion(boolean verbose
- ) throws IOException, InterruptedException,
- ClassNotFoundException {
- if (state == JobState.DEFINE) {
- submit();
- }
- if (verbose) {
- jobClient.monitorAndPrintJob(conf, info);
- } else {
- info.waitForCompletion();
- }
- return isSuccessful();
- }
2.job.submit():在submit中會調用setUseNewAPI(),setUseNewAPI()這個方法主要是判斷是使用新的api還是舊的api,之後會調用connect()方法,該方法主要是實例化jobClient,然後會調用jobClient.submitJobInternal(conf)這個方法進行job的提交
- public void submit() throws IOException, InterruptedException,
- ClassNotFoundException {
- ensureState(JobState.DEFINE);
- setUseNewAPI();
- // Connect to the JobTracker and submit the job
- connect();
- info = jobClient.submitJobInternal(conf);
- super.setJobID(info.getID());
- state = JobState.RUNNING;
- }
3.jobClient.submitJobInternal():這個方法會將job運行時所需的所有文件上傳到jobTarcker文件系統(一般是hdfs)中,同時進行備份(備份數默認是10,通過mapred.submit.replication變量可以設置),這個方法需要深入進行解讀。
4.JobSubmissionFiles.getStagingDir:這個方法是在jobClient.submitJobInternal()最先調用的,這個方法主要是獲取一個job提交的根目錄,主要是通過Path stagingArea = client.getStagingAreaDir();方法獲得,這個方法最終會調用jobTracker.getStagingAreaDirInternal()方法,代碼如下:
- private String getStagingAreaDirInternal(String user) throws IOException {
- final Path stagingRootDir =
- new Path(conf.get("mapreduce.jobtracker.staging.root.dir",
- "/tmp/hadoop/mapred/staging"));
- final FileSystem fs = stagingRootDir.getFileSystem(conf);
- return fs.makeQualified(new Path(stagingRootDir,
- user+"/.staging")).toString();
- }
在獲取了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的分片操作。
- private int writeSplits(org.apache.hadoop.mapreduce.JobContext job,
- Path jobSubmitDir) throws IOException,
- InterruptedException, ClassNotFoundException {
- JobConf jConf = (JobConf)job.getConfiguration();
- int maps;
- if (jConf.getUseNewMapper()) {
- maps = writeNewSplits(job, jobSubmitDir);
- } else {
- maps = writeOldSplits(jConf, jobSubmitDir);
- }
- return maps;
- }
11.jobClient.writeNewSplits():這個方法主要是根據我們設置的inputFormat.class通過反射獲得inputFormat對象,然後調用inputFormat對象的getSplits方法,當獲得分片信息之後調用JobSplitWriter.createSplitFiles方法將分片的信息寫入到submitJobDir/job.split文件中。
- private <T extends InputSplit>
- int writeNewSplits(JobContext job, Path jobSubmitDir) throws IOException,
- InterruptedException, ClassNotFoundException {
- Configuration conf = job.getConfiguration();
- InputFormat<?, ?> input =
- ReflectionUtils.newInstance(job.getInputFormatClass(), conf);
- List<InputSplit> splits = input.getSplits(job);
- T[] array = (T[]) splits.toArray(new InputSplit[splits.size()]);
- // sort the splits into order based on size, so that the biggest
- // go first
- Arrays.sort(array, new SplitComparator());
- JobSplitWriter.createSplitFiles(jobSubmitDir, conf,
- jobSubmitDir.getFileSystem(conf), array);
- return array.length;
- }
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.繼續往下走:
- String queue = jobCopy.getQueueName();
- AccessControlList acl = jobSubmitClient.getQueueAdmins(queue);
- jobCopy.set(QueueManager.toFullPropertyName(queue,
- 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的內容:
- Class<? extends TaskScheduler> schedulerClass
- = 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方法了
- public static JobTracker startTracker(JobConf conf, String identifier, boolean initialize)
- throws IOException, InterruptedException {
- DefaultMetricsSystem.initialize("JobTracker");
- JobTracker result = null;
- while (true) {
- try {
- result = new JobTracker(conf, identifier);
- result.taskScheduler.setTaskTrackerManager(result);
- break;
- } catch (VersionMismatch e) {
- throw e;
- } catch (BindException e) {
- throw e;
- } catch (UnknownHostException e) {
- throw e;
- } catch (AccessControlException ace) {
- // in case of jobtracker not having right access
- // bail out
- throw ace;
- } catch (IOException e) {
- LOG.warn("Error starting tracker: " +
- StringUtils.stringifyException(e));
- }
- Thread.sleep(1000);
- }
- if (result != null) {
- JobEndNotifier.startNotifier();
- MBeans.register("JobTracker", "JobTrackerInfo", result);
- if(initialize == true) {
- result.setSafeModeInternal(SafeModeAction.SAFEMODE_ENTER);
- result.initializeFilesystem();
- result.setSafeModeInternal(SafeModeAction.SAFEMODE_LEAVE);
- result.initialize();
- }
- }
- return result;
- }
.JobTracker.main():在實例化jobTracker之後,會調用tracker.offerService()方法,
之後main方法就沒什麼了,下面看看tracker.offerService()這個方法
- public static void main(String argv[]
- ) throws IOException, InterruptedException {
- StringUtils.startupShutdownMessage(JobTracker.class, argv, LOG);
- try {
- if(argv.length == 0) {
- JobTracker tracker = startTracker(new JobConf());
- tracker.offerService();
- }
- else {
- if ("-dumpConfiguration".equals(argv[0]) && argv.length == 1) {
- dumpConfiguration(new PrintWriter(System.out));
- }
- else {
- System.out.println("usage: JobTracker [-dumpConfiguration]");
- System.exit(-1);
- }
- }
- } catch (Throwable e) {
- LOG.fatal(StringUtils.stringifyException(e));
- System.exit(-1);
- }
- }
只看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()方法
- this.localJobFile = default_conf.getLocalPath(JobTracker.SUBDIR
- +"/"+jobId + ".xml");
- Path jobFilePath = JobSubmissionFiles.getJobConfPath(jobSubmitDir);
- jobFile = jobFilePath.toString();
- fs.copyToLocalFile(jobFilePath, localJobFile);
- 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。
- private synchronized JobStatus addJob(JobID jobId, JobInProgress job)
- throws IOException {
- totalSubmissions++;
- synchronized (jobs) {
- synchronized (taskScheduler) {
- jobs.put(job.getProfile().getJobID(), job);
- for (JobInProgressListener listener : jobInProgressListeners) {
- listener.jobAdded(job);
- }
- }
- }
- myInstrumentation.submitJob(job.getJobConf(), jobId);
- job.getQueueMetrics().submitJob(job.getJobConf(), jobId);
- LOG.info("Job " + jobId + " added successfully for user '"
- + job.getJobConf().getUser() + "' to queue '"
- + job.getJobConf().getQueueName() + "'");
- AuditLogger.logSuccess(job.getUser(),
- Operation.SUBMIT_JOB.name(), jobId.toString());
- return job.getStatus();
- }
==================== 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也就差不多完成了,
- public synchronized void initTasks()
- throws IOException, KillInterruptedException, UnknownHostException {
- // log the job priority
- setPriority(this.priority);
- //........
- numMapTasks = splits.length;
- // 添加Map 和 reduce
- jobtracker.getInstrumentation().addWaitingMaps(getJobID(), numMapTasks);
- jobtracker.getInstrumentation().addWaitingReduces(getJobID(), numReduceTasks);
- //....
- //create map;
- maps = new TaskInProgress[numMapTasks];
- //....
- //create reduce
- this.reduces = new TaskInProgress[numReduceTasks];
- //下面是cleanup 和log
- //....
- }