函数调用分析.docx
《函数调用分析.docx》由会员分享,可在线阅读,更多相关《函数调用分析.docx(41页珍藏版)》请在冰豆网上搜索。
函数调用分析
1引言
1.1致谢
作者是在读了“觉先”的博客《Hadoop学习总结之四:
Map-Reduce的过程解析》之后才从宏观上了解HadoopMapReduce模块的工作原理,并且以此为蓝本,写出了本文。
所以,首先在此向“觉先”表示敬意。
另外本文当中可能有很多地方直接引用前述博文,在此特别声明,后面就不一一标注了。
1.2目的
该文档从源代码的级别剖析了Hadoop0.20.2版本的MapReduce模块的运行原理和流程,对JobTracker、TaskTracker的内部结构和交互流程做了详细介绍。
系统地分析了Map程序和Reduce程序运行的原理。
读者在阅读之后会对HadoopMapReduce0.20.2版本源代码有一个大致的认识。
1.3读者范围
如果读者想只是想从原理上更加深入了解HadoopMapReduce运行机制的话,只需要阅读第2章综述即可,该章节要求读者对HadoopMapReduce模型有系统的了解。
如果读者想深入了解HadoopMapReduce的源代码,则需阅读该文档第2、3节。
阅读第3节需要读者熟练掌握Java语言的基本语法,并且对反射机制、动态代理有一定的了解。
同时,还要求读者对于HadoopHDFS和HadoopRPC的基本用法有一定的了解。
另外,属性Hadoop源代码的最好方法是远程调试,有关远程调试的方法请读者去网上自行查阅资料。
1.4术语定义
Job:
用户所需要完成的工作;
Task:
一个Job可以分解为多个Task,每一个Task都是一个独立的运行单元,多个Task并行运行,从而实现了MapReduce的并行计算。
JobTracker:
在HadoopMapReduce模型中负责Job调度的角色;
TaskTracker:
在HadoopMapReduce模型中负责Task调度的角色;
2综述
Hadoop源代码分为三大模块:
MapReduce、HDFS和HadoopCommon。
其中MapReduce模块主要实现了MapReduce模型的相关功能;HDFS模块主要实现了HDFS的相关功能;而HadoopCommon主要实现了一些基础功能,比如说RPC、网络通信等。
在用户使用HadoopMapReduce模型进行并行计算时,用户只需要写好Map函数、Reduce函数,之后调用JobClient将Job提交即可。
在JobTracker收到提交的Job之后,便会对Job进行一系列的配置,然后交给TaskTracker进行执行。
执行完毕之后,JobTracker会通知JobClient任务完成,并将结果存入HDFS中
如图所示,用户提交Job是通过JobClient类的submitJob()函数实现的。
在Hadoop源代码中,一个被提交了的Job由JobInProgress类的一个实例表示。
该类封装了表示Job的各种信息,以及Job所需要执行的各种动作。
在调用submitJob()函数之后,JobTracker会将作业加入到一个队列中去,这个队列的名字叫做jobInitQueue。
然后,在JobTracker中,有一个名为JobQueueTaskScheduler的对象,会不断轮询jobInitQueue队列,一旦发现有新的Job加入,便将其取出,然后将其初始化。
在Hadoop代码中,一个Task由一个TaskInProgress类的实例表示。
该类封装了描述Task所需的各种信息以及Task执行的各种动作。
TaskTracker自从启动以后,会每隔一段时间向JobTracker发送消息,消息的名称为“Heartbeat”。
Heartbeat中包含了该TaskTracker当前的状态以及对Task的请求。
JobTracker在收到Heartbeat之后,会检查该heartbeat的里所包含的各种信息,如果发现错误会启动相应的错误处理程序。
如果TaskTracker在Heartbeat中添加了对Task的请求,则JobTracker会添加相应的指令在对Heartbeat的回复中。
在Hadoop源代码中,JobTracker对TaskTracker的指令称为action,JobTracker对TaskTracker所发送来的Heartbeat的回复消息称为HeartbeatResponse。
在TaskTracker内部,有一个队列叫做TaskQueue。
该中包含了所有新加入的Task。
每当TaskTracker收到HeartbeatResponse后,会对其进行检查,如果其中包含了新的Task,便将其加入到TaskQueue中。
在TaskTracker内部,有两个线程不断轮询TaskQueue,一个是MapLauncher,另一个是ReduceLauncher。
如果发现有新加入的Map任务,MapLauncher便将其取出并且执行。
如果是Reduce任务,ReduceLauncher便将其取出执行。
不论是MapTask还是ReduceTask,当他们被取出之后,都要进行本地化。
本地化的意思就是将所有需要的信息,比如需要运行的jar文件、配置文件、输入数据等等,一起拷贝到本地的文件系统。
这样做的目的是为了方便任务在某台机器上独立执行。
本地化之后,TaskTracker会为每一个task单独创建一个jvm,然后单独运行。
等Task运行完之后,TaskTracker会通知JobTracker任务完成,以进行下一步的动作。
等到所有的Task都完成之后,Job也就完成了,此时JobTracker会通知JobClient工作完成。
3代码详细分析
下面从用户使用Hadoop进行MapReduce计算的过程为线索,详细介绍Task执行的细节,并对HadoopMapReduce的主要代码进行分析。
3.1启动Hadoop集群
Hadoop集群的启动是通过在Master上运行start-all.sh脚本进行的。
运行该脚本之后,Hadoop会配置一系列的环境变量以及其他Hadoop运行所需要的参数,然后在本机运行JobTracker和NameNode。
然后通过SSH登录到所有slave机器上,启动TaskTracker和DataNode。
因为本文只介绍HadoopMapReduce模块,所以NameNode和DataNode的相关知识不再介绍。
3.2JobTracker启动以及Job的初始化
org.apache.hadoop.mapred.JobTracker类实现了HadoopMapReduce模型的JobTracker的功能,主要负责任务的接受,初始化,调度以及对TaskTracker的监控。
JobTracker单独作为一个JVM运行,main函数就是启动JobTracker的入口函数。
在main函数中,有以下两行非常重要的代码:
startTracker(newJobConf());
JobTracker.offerService();
startTracker函数是一个静态函数,它调用JobTracker的构造函数生成一个JobTracker类的实例,名为result。
然后,进行了一系列初始化活动,包括启动RPCserver,启动内置的jetty服务器,检查是否需要重启JobTracker等。
在JobTracker.offerService()中,调用了taskScheduler对象的start()方法。
该对象是JobTracker的一个数据成员,类型为TaskScheduler。
该类型的提供了一系列接口,使得JobTracker可以对所有提交的job进行初始化以及调度。
但是该类型实际上是一个抽象类型,其真正的实现类型为JobQueueTaskScheduler类,所以,taskScheduler.start()方法执行的是JobQueueTaskScheduler类的start方法。
该方法的详细代码如下:
publicsynchronizedvoidstart()throwsIOException{
//调用TaskScheduler.start()方法,实际上没有做任何事情
super.start();
//注册一个JobInProgressListerner监听器
taskTrackerManager.addJobInProgressListener(jobQueueJobInProgressListener
);
eagerTaskInitializationListener.setTaskTrackerManager(taskTrackerManager);
eagerTaskInitializationListener.start();
taskTrackerManager.addJobInProgressListener(eagerTaskInitializationListener)
}
JobQueueTaskScheduler类的start方法主要注册了两个非常重要的监听器:
jobQueueJobInProgressListener和eagerTaskInitializationListener。
前者是JobQueueJobInProgressListener类的一个实例,该类以先进先出的方式维持一个JobInProgress的队列,并且监听各个JobInProgress实例在生命周期中的变化;后者是EagerTaskInitializationListener类的一个实例,该类不断监听jobInitQueue,一旦发现有新的job被提交(即有新的JobInProgress实例被加入),则立即调用该实例的initTasks方法,对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;iinputLength+=splits[i].getDataLength();
maps[i]=newTaskInProgress(jobId,jobFile,
splits[i],
jobtracker,conf,this,i);
}
/*
对于maptask,将其放入nonRunningMapCache,是一个MapList>,也即对于maptask来讲,其将会被分配到其input
split所在的Node上。
在此,Node代表一个datanode或者机架或者数据中
心。
nonRunningMapCache将在JobTracker向TaskTracker分配maptask的
时候使用。
*/
if(numMapTasks>0){
nonRunningMapCache=createCache(splits,maxLevel);
}
//创建reducetask
this.reduces=newTaskInProgress[numReduceTasks];
for(inti=0;ireduces[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);//初始化完毕
……
}
3.3TaskTracker启动以及发送Heartbeat
org.apache.hadoop.mapred.TaskTracker类实现了MapReduce模型中TaskTracker的功能。
TaskTracker也是作为一个单独的JVM来运行的,其main函数就是TaskTracker的入口函数,当运行start-all.sh时,脚本就是通过SSH运行该函数来启动TaskTracker的。
Main函数中最重要的语句是:
newTaskTracker(conf).run();
其中run函数主要调用了offerService函数:
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函数的作用就是第2章中提到的向JobTracker发送Heartbeat。
其主要逻辑如下:
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;
}
3.4JobTracker接收Heartbeat并向TaskTracker分配任务
当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){
//任务调度器分配