Hadoop任務提交分析
分析工具和環境
下載一份hadoop的源碼,這裏以hadoop-1.1.2爲例。本地IDE環境爲eclipse,導入整個目錄,然後可以在IDE裏面看到目錄結構了,要分析任務提交過程,需要找到入口代碼,很明顯,對於熟悉Hadoop應用程序開發的同學來說很容易的知道任務的提交是從job的配置開始的,所以需要這樣一份提交Job的代碼,在src/examples裏面你可以找到一些例子,這裏從最簡單的wordcount進入去分析提交過程。
核心的一些類
Configuration Configuration類是整個框架的配置信息的集合類,裏面主要包含了overlay
和property
和resources
,而主要的方法主要看get
和set
,下面具體看一下這幾個變量和方法overlay
: private
Properties overlay;
,其實overlay是Properties對象,用來存儲key-value pair,但是它的主要作用還在於在添加Resource後,保證Resources重載後用戶通過set函數設置的屬性對會覆蓋Resource中相同key的屬性對。 properties
:
用來存儲key-value pair finalParameters
:是一個set,用來保存final的配置信息,即不允許覆蓋
addResourceObject會添加資源並調用reloadConfiguration,在reloadConfiguration中會清空properties對象和finalParameter,然後下次通過get去讀某個屬性的時候,會由於properties爲null而,重新載入resources,這樣資源得到更新,並且,在這之後立馬會將overlay裏面的內容覆蓋進入properties,就是將之前通過set函數設置的屬性對去覆蓋resource中的默認屬性對。
private synchronized void addResourceObject(Object resource) {
resources.add(resource); // add to resources
reloadConfiguration();
}
public synchronized void reloadConfiguration() {
properties = null; // trigger reload
finalParameters.clear(); // clear site-limits
}
public String get(String name) {
return substituteVars(getProps().getProperty(name)); //這裏先是getProps得到properties對象然後調用getProperty得到value,然後通過
//substitudeVars函數去替換value中的${..},其實就是引用變量
}
private synchronized Properties getProps() {
if (properties == null) { //properties爲null會重新實例化properties,然後載入資源,並將overlay裏面的內容去覆蓋properties裏面相同key
properties = new Properties(); //的內容
loadResources(properties, resources, quietmode);
if (overlay!= null) {
properties.putAll(overlay);
for (Map.Entry<Object,Object> item: overlay.entrySet()) {
updatingResource.put((String) item.getKey(), UNKNOWN_RESOURCE);
}
}
}
return properties;
}
Job
Job類是hadoop任務配置的對象,繼承於JobContext,而他們的構造函數都其實是以Configuration對象作爲參數,然後提供了一些設置Job配置信息或者獲取Job配置信息的一些函數。
上述的配置Job信息的函數並不是直接使用了Configuration對象,而是使用了Configuration對象的子類JobConf,JobContext以conf = JobConf(conf)作爲內置成員變量。JobConf是在Confguration的基礎上提供了Job配置的一些函數
Job類配置好類以及輸入輸出等之後,會調用Job.waitforcompletion當然也可以直接調用submit函數,其中waitForCompletion會調用submit然後jobClient.monitorAndPrintJob打印和監視Job的運行進度和情況。
submit函數分析
public void submit() throws IOException, InterruptedException,
ClassNotFoundException {
ensureState(JobState.DEFINE);
setUseNewAPI();
// Connect to the JobTracker and submit the job
connect(); //主要是生成了jobClient對象,而在該對象的初始化過程中,主要生成 了jobSubmitClient的一個動態代理,能夠RPC調用
//JobTracker中的一些函數(分佈式的情況,本地情況先不做分析)
info = jobClient.submitJobInternal(conf); // 任務提交的過程
super.setJobID(info.getID());
state = JobState.RUNNING;
}
jobClient構造函數分析
//jobClient的構造函數,設置了conf,然後就調用了init函數
public JobClient(JobConf conf) throws IOException {
setConf(conf);
init(conf);
}
/**
* 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); //本地模式下,會生成一個LocalJobRunner對象
} else {
this.rpcJobSubmitClient =
createRPCProxy(JobTracker.getAddress(conf), conf); // 分佈式模式下,會生成了一個RPC代理
this.jobSubmitClient = createProxy(this.rpcJobSubmitClient, conf); //對這個代理再次封裝了一下,其實裏面主要是增加了retry的一些代碼
}
}
jobClient.submitJobInternal函數分析
public
RunningJob submitJobInternal(final JobConf job
) throws FileNotFoundException,
ClassNotFoundException,
InterruptedException,
IOException {
/*
* configure the command line options correctly on the submitting dfs
*/
return ugi.doAs(new PrivilegedExceptionAction<RunningJob>() {
public RunningJob run() throws FileNotFoundException,
ClassNotFoundException,
InterruptedException,
IOException{
JobConf jobCopy = job;
Path jobStagingArea = JobSubmissionFiles.getStagingDir(JobClient.this,
jobCopy); //獲得任務提交的目錄,其實是作爲參數的JobClient去通過RPC從JobTracker那裏獲取得到jobStagingArea
// ${hadoop.tmp.dir}/mapred/staging/user-name/.staging目錄
JobID jobId = jobSubmitClient.getNewJobId(); //從JobTracker那裏獲得分配的jobid
Path submitJobDir = new Path(jobStagingArea, jobId.toString()); //submitJobDir = ${jobStagingArea}/job-id
jobCopy.set("mapreduce.job.dir", submitJobDir.toString());
JobStatus status = null;
try {
populateTokenCache(jobCopy, jobCopy.getCredentials());
copyAndConfigureFiles(jobCopy, submitJobDir); // 這個函數主要是負責處理通過參數--libjars,--archives,--files引入的jar包,以及要使用分佈式緩存的文件和archive,並且將job.jar也拷貝到分佈式文件系統上
// get delegation token for the dir
TokenCache.obtainTokensForNamenodes(jobCopy.getCredentials(),
new Path [] {submitJobDir},
jobCopy);
Path submitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir);
int reduces = jobCopy.getNumReduceTasks();
InetAddress ip = InetAddress.getLocalHost();
if (ip != null) {
job.setJobSubmitHostAddress(ip.getHostAddress());
job.setJobSubmitHostName(ip.getHostName());
}
JobContext context = new JobContext(jobCopy, jobId);
//檢查output,如果已經存在則會拋出異常
// Check the output specification
if (reduces == 0 ? jobCopy.getUseNewMapper() :
jobCopy.getUseNewReducer()) {
org.apache.hadoop.mapreduce.OutputFormat<?,?> output =
ReflectionUtils.newInstance(context.getOutputFormatClass(),
jobCopy);
output.checkOutputSpecs(context);
} else {
jobCopy.getOutputFormat().checkOutputSpecs(fs, jobCopy);
}
jobCopy = (JobConf)context.getConfiguration();
//利用設置好的inputformat的來對輸入進行分片,然後將分片寫入到job.split中,然後也會將分片的元信息寫入到job.splitmetainfo中
//
// Create the splits for the job
FileSystem fs = submitJobDir.getFileSystem(jobCopy);
LOG.debug("Creating splits at " + fs.makeQualified(submitJobDir));
int maps = writeSplits(context, submitJobDir);
jobCopy.setNumMapTasks(maps);
// write "queue admins of the queue to which job is being submitted"
// to job file.
String queue = jobCopy.getQueueName();
AccessControlList acl = jobSubmitClient.getQueueAdmins(queue);
jobCopy.set(QueueManager.toFullPropertyName(queue,
QueueACL.ADMINISTER_JOBS.getAclName()), acl.getACLString());
//將job配置相關的信息寫入到job.xml中
// Write job file to JobTracker's fs
FSDataOutputStream out =
FileSystem.create(fs, submitJobFile,
new FsPermission(JobSubmissionFiles.JOB_FILE_PERMISSION));
try {
jobCopy.writeXml(out); //正式寫入
} finally {
out.close();
}
//
// Now, actually submit the job (using the submit name)
//
printTokens(jobId, jobCopy.getCredentials());
status = jobSubmitClient.submitJob(
jobId, submitJobDir.toString(), jobCopy.getCredentials()); //通過RPC提交任務給JobTracker,讓其去管理任務,在JobTrakcker的
//subnitJob裏面會實例化JobInProgress並保存在一個jobs的Map<jobID,JobInProgress>的map裏面
JobProfile prof = jobSubmitClient.getJobProfile(jobId); //獲得任務的概要信息,主要包括了jobname,jobid,提交的隊列名,job配置信息的路徑,用戶,web接口的url等
if (status != null && prof != null) {
return new NetworkedJob(status, prof, jobSubmitClient); //對status,prof,以及jobSubmitClient的封裝,主要用來可以查詢進度信息和profile信息。而status裏面提供了getMapProgress和setMapProgress類型的接口函數
} else {
throw new IOException("Could not launch job");
}
} finally {
if (status == null) {
LOG.info("Cleaning up the staging area " + submitJobDir);
if (fs != null && submitJobDir != null)
fs.delete(submitJobDir, true);
}
}
}
});
}
JobClient
從上面的一些代碼也可以看出真正提交Job是在JobClient做的,它主要處理了一些參數像libjars files archives把他們指定的文件拷貝到HDFS的submitJobDir,然後把JOb的配置信息job.xml , 分片信息以及job.jar也都拷貝到HDFS上。然後利用jobClient裏面的一個代理類jobSubmitClient來提交任務,其實就是一個RPC請求,向JObTracker發送請求,然後返回的是一個JobStatus類型的對象,該對象可以查詢map reduce進度等。
下一篇講一下JObTracker相關