Hadoop学习总结之四MapReduce的过程解析.docx
《Hadoop学习总结之四MapReduce的过程解析.docx》由会员分享,可在线阅读,更多相关《Hadoop学习总结之四MapReduce的过程解析.docx(33页珍藏版)》请在冰豆网上搜索。
Hadoop学习总结之四MapReduce的过程解析
一、客户端
Map-Reduce的过程首先是由客户端提交一个任务开始的。
提交任务主要是通过JobClient.runJob(JobConf)静态函数实现的:
publicstaticRunningJobrunJob(JobConfjob)throwsIOException{
//首先生成一个JobClient对象
JobClientjc=newJobClient(job);
……
//调用submitJob来提交一个任务
running=jc.submitJob(job);
JobIDjobId=running.getID();
……
while(true){
//while循环中不断得到此任务的状态,并打印到客户端console中
}
returnrunning;
}
其中JobClient的submitJob函数实现如下:
publicRunningJobsubmitJob(JobConfjob)throwsFileNotFoundException,
InvalidJobConfException,IOException{
//从JobTracker得到当前任务的id
JobIDjobId=jobSubmitClient.getNewJobId();
//准备将任务运行所需要的要素写入HDFS:
//任务运行程序所在的jar封装成job.jar
//任务所要处理的inputsplit信息写入job.split
//任务运行的配置项汇总写入job.xml
PathsubmitJobDir=newPath(getSystemDir(),jobId.toString());
PathsubmitJarFile=newPath(submitJobDir,"job.jar");
PathsubmitSplitFile=newPath(submitJobDir,"job.split");
//此处将-libjars命令行指定的jar上传至HDFS
configureCommandLineOptions(job,submitJobDir,submitJarFile);
PathsubmitJobFile=newPath(submitJobDir,"job.xml");
……
//通过inputformat的格式获得相应的inputsplit,默认类型为FileSplit
InputSplit[]splits=
job.getInputFormat().getSplits(job,job.getNumMapTasks());
//生成一个写入流,将inputsplit得信息写入job.split文件
FSDataOutputStreamout=FileSystem.create(fs,
submitSplitFile,newFsPermission(JOB_FILE_PERMISSION));
try{
//写入job.split文件的信息包括:
split文件头,split文件版本号,split的个数,接着依次写入每一个inputsplit的信息。
//对于每一个inputsplit写入:
split类型名(默认FileSplit),split的大小,split的内容(对于FileSplit,写入文件名,此split在文件中的起始位置),split的location信息(即在那个DataNode上)。
writeSplitsFile(splits,out);
}finally{
out.close();
}
job.set("mapred.job.split.file",submitSplitFile.toString());
//根据split的个数设定maptask的个数
job.setNumMapTasks(splits.length);
//写入job的配置信息入job.xml文件
out=FileSystem.create(fs,submitJobFile,
newFsPermission(JOB_FILE_PERMISSION));
try{
job.writeXml(out);
}finally{
out.close();
}
//真正的调用JobTracker来提交任务
JobStatusstatus=jobSubmitClient.submitJob(jobId);
……
}
二、JobTracker
JobTracker作为一个单独的JVM运行,其运行的main函数主要调用有下面两部分:
∙调用静态函数startTracker(newJobConf())创建一个JobTracker对象
∙调用JobTracker.offerService()函数提供服务
在JobTracker的构造函数中,会生成一个taskScheduler成员变量,来进行Job的调度,默认为JobQueueTaskScheduler,也即按照FIFO的方式调度任务。
在offerService函数中,则调用taskScheduler.start(),在这个函数中,为JobTracker(也即taskScheduler的taskTrackerManager)注册了两个Listener:
∙JobQueueJobInProgressListenerjobQueueJobInProgressListener用于监控job的运行状态
∙EagerTaskInitializationListenereagerTaskInitializationListener用于对Job进行初始化
EagerTaskInitializationListener中有一个线程JobInitThread,不断得到jobInitQueue中的JobInProgress对象,调用JobInProgress对象的initTasks函数对任务进行初始化操作。
在上一节中,客户端调用了JobTracker.submitJob函数,此函数首先生成一个JobInProgress对象,然后调用addJob函数,其中有如下的逻辑:
synchronized(jobs){
synchronized(taskScheduler){
jobs.put(job.getProfile().getJobID(),job);
//对JobTracker的每一个listener都调用jobAdded函数
for(JobInProgressListenerlistener:
jobInProgressListeners){
listener.jobAdded(job);
}
}
}
EagerTaskInitializationListener的jobAdded函数就是向jobInitQueue中添加一个JobInProgress对象,于是自然触发了此Job的初始化操作,由JobInProgress得initTasks函数完成:
publicsynchronizedvoidinitTasks()throwsIOException{
……
//从HDFS中读取job.split文件从而生成inputsplits
StringjobFile=profile.getJobFile();
PathsysDir=newPath(this.jobtracker.getSystemDir());
FileSystemfs=sysDir.getFileSystem(conf);
DataInputStreamsplitFile=
fs.open(newPath(conf.get("mapred.job.split.file")));
JobClient.RawSplit[]splits;
try{
splits=JobClient.readSplitFile(splitFile);
}finally{
splitFile.close();
}
//maptask的个数就是inputsplit的个数
numMapTasks=splits.length;
//为每个maptasks生成一个TaskInProgress来处理一个inputsplit
maps=newTaskInProgress[numMapTasks];
for(inti=0;i inputLength+=splits[i].getDataLength();
maps[i]=newTaskInProgress(jobId,jobFile,
splits[i],
jobtracker,conf,this,i);
}
//对于maptask,将其放入nonRunningMapCache,是一个Map>,也即对于maptask来讲,其将会被分配到其inputsplit所在的Node上。
nonRunningMapCache将在JobTracker向TaskTracker分配maptask的时候使用。
if(numMapTasks>0){
nonRunningMapCache=createCache(splits,maxLevel);
}
//创建reducetask
this.reduces=newTaskInProgress[numReduceTasks];
for(inti=0;i reduces[i]=newTaskInProgress(jobId,jobFile,
numMapTasks,i,
jobtracker,conf,this);
//reducetask放入nonRunningReduces,其将在JobTracker向TaskTracker分配reducetask的时候使用。
nonRunningReduces.add(reduces[i]);
}
//创建两个cleanuptask,一个用来清理map,一个用来清理reduce.
cleanup=newTaskInProgress[2];
cleanup[0]=newTaskInProgress(jobId,jobFile,splits[0],
jobtracker,conf,this,numMapTasks);
cleanup[0].setJobCleanupTask();
cleanup[1]=newTaskInProgress(jobId,jobFile,numMapTasks,
numReduceTasks,jobtracker,conf,this);
cleanup[1].setJobCleanupTask();
//创建两个初始化task,一个初始化map,一个初始化reduce.
setup=newTaskInProgress[2];
setup[0]=newTaskInProgress(jobId,jobFile,splits[0],
jobtracker,conf,this,numMapTasks+1);
setup[0].setJobSetupTask();
setup[1]=newTaskInProgress(jobId,jobFile,numMapTasks,
numReduceTasks+1,jobtracker,conf,this);
setup[1].setJobSetupTask();
tasksInited.set(true);//初始化完毕
……
}
三、TaskTracker
TaskTracker也是作为一个单独的JVM来运行的,在其main函数中,主要是调用了newTaskTracker(conf).run(),其中run函数主要调用了:
StateofferService()throwsException{
longlastHeartbeat=0;
//TaskTracker进行是一直存在的
while(running&&!
shuttingDown){
……
longnow=System.currentTimeMillis();
//每隔一段时间就向JobTracker发送heartbeat
longwaitTime=heartbeatInterval-(now-lastHeartbeat);
if(waitTime>0){
synchronized(finishedCount){
if(finishedCount[0]==0){
finishedCount.wait(waitTime);
}
finishedCount[0]=0;
}
}
……
//发送Heartbeat到JobTracker,得到response
HeartbeatResponseheartbeatResponse=transmitHeartBeat(now);
……
//从Response中得到此TaskTracker需要做的事情
TaskTrackerAction[]actions=heartbeatResponse.getActions();
……
if(actions!
=null){
for(TaskTrackerActionaction:
actions){
if(actioninstanceofLaunchTaskAction){
//如果是运行一个新的Task,则将Action添加到任务队列中
addToTaskQueue((LaunchTaskAction)action);
}elseif(actioninstanceofCommitTaskAction){
CommitTaskActioncommitAction=(CommitTaskAction)action;
if(!
commitResponses.contains(commitAction.getTaskID())){
commitResponses.add(commitAction.getTaskID());
}
}else{
tasksToCleanup.put(action);
}
}
}
}
returnState.NORMAL;
}
其中transmitHeartBeat主要逻辑如下:
privateHeartbeatResponsetransmitHeartBeat(longnow)throwsIOException{
//每隔一段时间,在heartbeat中要返回给JobTracker一些统计信息
booleansendCounters;
if(now>(previousUpdate+COUNTER_UPDATE_INTERVAL)){
sendCounters=true;
previousUpdate=now;
}
else{
sendCounters=false;
}
……
//报告给JobTracker,此TaskTracker的当前状态
if(status==null){
synchronized(this){
status=newTaskTrackerStatus(taskTrackerName,localHostname,
httpPort,
cloneAndResetRunningTaskStatuses(
sendCounters),
failures,
maxCurrentMapTasks,
maxCurrentReduceTasks);
}
}
……
//当满足下面的条件的时候,此TaskTracker请求JobTracker为其分配一个新的Task来运行:
//当前TaskTracker正在运行的maptask的个数小于可以运行的maptask的最大个数
//当前TaskTracker正在运行的reducetask的个数小于可以运行的reducetask的最大个数
booleanaskForNewTask;
longlocalMinSpaceStart;
synchronized(this){
askForNewTask=(status.countMapTasks() status.countReduceTasks() acceptNewTasks;
localMinSpaceStart=minSpaceStart;
}
……
//向JobTracker发送heartbeat,这是一个RPC调用
HeartbeatResponseheartbeatResponse=jobClient.heartbeat(status,
justStarted,askForNewTask,
heartbeatResponseId);
……
returnheartbeatResponse;
}
四、JobTracker
当JobTracker被RPC调用来发送heartbeat的时候,JobTracker的heartbeat(TaskTrackerStatusstatus,booleaninitialContact,booleanacceptNewTasks,shortresponseId)函数被调用:
publicsynchronizedHeartbeatResponseheartbeat(TaskTrackerStatusstatus,
booleaninitialContact,booleanacceptNewTasks,shortresponseId)
throwsIOException{
……
StringtrackerName=status.getTrackerName();
……
shortnewResponseId=(short)(responseId+1);
……
HeartbeatResponseresponse=newHeartbeatResponse(newResponseId,null);
Listactions=newArrayList();
//如果TaskTracker向JobTracker请求一个task运行
if(acceptNewTasks){
TaskTrackerStatustaskTrackerStatus=getTaskTracker(trackerName);
if(taskTrackerStatus==null){
LOG.warn("Unknowntasktrackerpolling;ignoring:
"+trackerName);
}else{
//setup和cleanup的task优先级最高
Listtasks=getSetupAndCleanupTasks(taskTrackerStatus);
if(tasks==null){
//任务调度器分配任务
tasks=taskScheduler.assignTasks(task