前言:本文旨在理清在Hadoop中一個MapReduce作業(Job)在提交到框架後的整個生命週期過程,權作總結和日後參考,如有問題,請不吝賜教。本文不涉及Hadoop的架構設計,如有興趣請參考相關書籍和文獻。在梳理過程中,我對一些感興趣的源碼也會逐行研究學習,以期強化基礎。
作者:Jaytalent
開始日期:2013年9月9日參考資料:【1】《Hadoop技術內幕--深入解析MapReduce架構設計與實現原理》董西成【2】Hadoop 1.0.0 源碼
status = jobSubmitClient.submitJob(jobId, submitJobDir.toString(), jobCopy.getCredentials());
這個方法所屬的接口在上一篇文章中有所提及,其實現有兩個:JobTracker和LocalRunner。LocalRunner是用於執行本地作業的,當Hadoop配置爲本地模式時採用該類處理作業。我們關注JobTracker.submitJob方法。這裏多說一句,在JobClient對象初始化時有如下代碼: /**
* Connect to the default {@link JobTracker}.
* @param conf the job configuration.
* @throws IOException
*/
public void init(JobConf conf) throws IOException {
String tracker = conf.get("mapred.job.tracker", "local");
tasklogtimeout = conf.getInt(
TASKLOG_PULL_TIMEOUT_KEY, DEFAULT_TASKLOG_TIMEOUT);
this.ugi = UserGroupInformation.getCurrentUser();
if ("local".equals(tracker)) {
conf.setNumMapTasks(1);
this.jobSubmitClient = new LocalJobRunner(conf);
} else {
this.jobSubmitClient = createRPCProxy(JobTracker.getAddress(conf), conf);
}
}
可以看到,tracker變量是從mapred.job.tracker配置獲取值的,默認值爲字符串“local”。因此,如果沒有在配置文件配置這個值或者JobConf對象中沒有添加配置文件(通常爲mapred-site.xml)資源,jobSubmitClient就會使用LocalJobRunner進行初始化。回到正題,JobTracker.submitJob方法會做如下工作:
// Create the JobInProgress, do not lock the JobTracker since
// we are about to copy job.xml from HDFS
JobInProgress job = null;
try {
job = new JobInProgress(this, this.conf, jobInfo, 0, ts);
} catch (Exception e) {
throw new IOException(e);
}
我們可以進入構造函數內部看看JobInProgress都有什麼內容。this.jobtracker = jobtracker;
this.status = new JobStatus(jobId, 0.0f, 0.0f, JobStatus.PREP);
JobStatus對象也比較關鍵,它維護了作業的一些狀態信息如作業優先級,開始時間,map和reduce任務的進度等。其他信息在後面分析JobTracker實現的時候再詳述。
// check if queue is RUNNING
String queue = job.getProfile().getQueueName();
if (!queueManager.isRunning(queue)) {
throw new IOException("Queue \"" + queue + "\" is not running");
}
try {
aclsManager.checkAccess(job, ugi, Operation.SUBMIT_JOB);
} catch (IOException ioe) {
LOG.warn("Access denied for user " + job.getJobConf().getUser()
+ ". Ignoring job " + jobId, ioe);
job.fail();
throw ioe;
}
c. 檢查作業的內存使用量。用戶在配置文件中可以配置Map任務和Reduce任務的內存使用量,而這些值不能超過管理員所配置的最大使用量,否則作業提交就會失敗。 // Check the job if it cannot run in the cluster because of invalid memory
// requirements.
try {
checkMemoryRequirements(job);
} catch (IOException ioe) {
throw ioe;
}
d. 調用調度器模塊,對作業進行初始化。具體過程如下 // Submit the job
JobStatus status;
status = addJob(jobId, job);
在addJob方法中,作業首先被加入到已經提交的作業列表中,然後通知JobTracker所有的監聽器對象,當前作業被提交,並採取相應的行動。 synchronized (jobs) {
synchronized (taskScheduler) {
jobs.put(job.getProfile().getJobID(), job);
for (JobInProgressListener listener : jobInProgressListeners) {
listener.jobAdded(job);
}
}
}
其中,任務調度器taskScheduler對象是在JobTracker構造時創建出來的。調度器對象和JobTracker對象是互相包含的關係。 // Create the scheduler
Class<? extends TaskScheduler> schedulerClass
= conf.getClass("mapred.jobtracker.taskScheduler",
JobQueueTaskScheduler.class, TaskScheduler.class);
taskScheduler = (TaskScheduler) ReflectionUtils.newInstance(schedulerClass, conf);
可以看出,Hadoop任務調度器時可插拔的模塊,調度器的類型通過配置文件獲得,並使用反射機制實例化。用戶可以通過繼承TaskScheduler類實現自己的調度器(由於做研究需要,本人實現了一個簡單的調度器,實現過程日後分享)。Hadoop默認的調度器爲JobQueueTaskScheduler,調度策略爲先進先出(FIFO)。 public synchronized void start() throws IOException {
super.start();
taskTrackerManager.addJobInProgressListener(jobQueueJobInProgressListener);
eagerTaskInitializationListener.setTaskTrackerManager(taskTrackerManager);
eagerTaskInitializationListener.start();
taskTrackerManager.addJobInProgressListener(
eagerTaskInitializationListener);
}
其中taskTrackerManager對象就是JobTracker,通過addJobInProgressListener方法註冊監聽器。這樣,當有JobTracker發現有作業被提交、更新或刪除時,就會通知訂閱者TaskScheduler,並調用相應的回調函數,如上面提到的listener.jobAdded方法。更多調度器的細節請關注後續文章。 // create two setup tips, one map and one reduce.
setup = new TaskInProgress[2];
// setup map tip. This map doesn't use any split. Just assign an empty
// split.
setup[0] = new TaskInProgress(jobId, jobFile, emptySplit,
jobtracker, conf, this, numMapTasks + 1, 1);
setup[0].setJobSetupTask();
// setup reduce tip.
setup[1] = new TaskInProgress(jobId, jobFile, numMapTasks,
numReduceTasks + 1, jobtracker, conf, this, 1);
setup[1].setJobSetupTask();
maps = new TaskInProgress[numMapTasks];
for(int i=0; i < numMapTasks; ++i) {
inputLength += splits[i].getInputDataLength();
maps[i] = new TaskInProgress(jobId, jobFile,
splits[i],
jobtracker, conf, this, i, numSlotsPerMap);
}
TaskInProgrees維護任務運行時信息,與JobInProgress類似。 this.reduces = new TaskInProgress[numReduceTasks];
for (int i = 0; i < numReduceTasks; i++) {
reduces[i] = new TaskInProgress(jobId, jobFile,
numMapTasks, i,
jobtracker, conf, this, numSlotsPerReduce);
nonRunningReduces.add(reduces[i]);
}
用戶可以在配置文件中指定Reduce任務的個數。 // create cleanup two cleanup tips, one map and one reduce.
cleanup = new TaskInProgress[2];
// cleanup map tip. This map doesn't use any splits. Just assign an empty
// split.
TaskSplitMetaInfo emptySplit = JobSplit.EMPTY_TASK_SPLIT;
cleanup[0] = new TaskInProgress(jobId, jobFile, emptySplit,
jobtracker, conf, this, numMapTasks, 1);
cleanup[0].setJobCleanupTask();
// cleanup reduce tip.
cleanup[1] = new TaskInProgress(jobId, jobFile, numMapTasks,
numReduceTasks, jobtracker, conf, this, 1);
cleanup[1].setJobCleanupTask();
- 作業初始化後會佔用內存資源,如果有大量初始化作業在JobTracker等待調度就會佔用不必要的資源。在交給調度器後,Hadoop按照一定策略選擇性地初始化以節省內存資源。
- 只有經過初始化的作業才能得到調度,因此將初始化工作嵌入調度器中比較合理。