在这两篇文章中(见 与 ),我详细的介绍了Job的任务分解以及Job任务的调度,同时也讲述了TaskTracker是每一通过向JobTracker发送心跳包heartbeat并从其响应中获取自己被分配的任务的。那么,在TaskTracker中,它是如何来执行这些Task的呢,或者说为这个Task的执行做了哪些准备工作呢?
其实,在一个TaskTracker节点中,每一Task的执行并不是在当前这个TaskTracker所属的进程中完成的,而是对每一个Task,都从新开启一个对应的JVM进程,让这个JVM进程来执行这个Task,同时这个JVM进程通过网络通行的方式来和开启它的TaskTracker进行交互,报告对应的Task的执行进度。这个执行模型可简单理解为:
在这里,我将详细为大家讨论TaskTracker是如何从接收到Task,到开始执行这个Task中间的过程,至于Child是如何执行Task的,在本文我不会作详细的讨论(但我会在以后的博文中详细的阐述)。我把这个过程成为Task的执行准备阶段,先来看看与之相关的类图吧!这个类体系图相当的复杂:
好了,我们再来看看与这些相关的时序图,以便更详细地了解TaskTracker是如何为一个新来的Task准备JVM运行环境的。
了解了每一个先关类及其方法的调用时序关系之后,就来看看这些类中的一些重要方法到底都具体干了那些事?
1.TaskTracker.registerTask()
该方法为新来的一个Task创建一个对应的TaskInProgress对象,同时在任务集合tasks和正在运行的任务集合中runningTasks中保存Task和TaskInProgress的映射关系,并根据Task的类型更新当前TaskTracker的map/reduce任务数量。
2.TaskTracker.addTaskToJob()
检查该Task所属的Job是否已在TaskTracker运行,如果没有,则创建该Job的一个RunningJob对象,并把该Task放到RunningJob中,最后放回该Task‘s Job的RunningJob。
3.TaskTracker.localizeJob()
如果Task对应的RunningJob还没有本地化,则需要本地化该Job。本地化该Job实际上就是根据JobID信息从HDFS中下载该Job对应的配置文件job.xml和运行文件job.jar到本地的文件系统。TaskTracker会为每一个Job创建一个目录,这个目录放在$(mapred.local.dir)/taskTracker/jobcache/下面,这里的$(mapred.local.dir)指的是mapreduce的配置文件中的mapred.local.dir项的值。Job的job.xml文件被放在Job的本地目录下:$(mapred.local.dir)/taskTracker/jobcache/jobid/job.xml,而Job的运行文件job.jar被放在Job的本地目录的子目录jars/下:$(mapred.local.dir)/taskTracker/jobcache/jobid/jars/job.jar,然后根据当前Job的实际配置信息重写job.xml文件,同时解压文件job.jar。
4.TaskInProgress.localizeTask()
首先会为该Task创建一个本地目录,目录名是该Task的id,这个目录在Task所属的Job的本地目录下:$(mapred.local.dir)/taskTracker/jobcache/jobid/taskid/,如果该Task的本地目录已存在,则需先清空该Task的本地目录。同时,在该目录下创建一个该Task被调用执行JVM时的工作目录:$(mapred.local.dir)/taskTracker/jobcache/jobid/taskid/work/,然后将当前Task的详细配置信息写入Task的配置文件$(mapred.local.dir)/taskTracker/jobcache/jobid/taskid/job.xml中。
5.TaskRunner.run()
TaskRunner会将Task运行所依赖的第三方工具包、归档文件、普通文件等从HDFS复制到TaskTracker的本地文件系统,并且包这些文件缓存起来,以供其他的Task使用。这些文件被保存在$(mapred.local.dir)/taskTracker/archive/下面,同时这些文件的相对路径在$(mapred.local.dir)/taskTracker/archive/下保持不变。然后会为启动该Task对应的JVM进程配置运行环境和参数。最后会通过Java程序来调用系统命令,即启动一个JVM进程来执行该Task,关于如何使用Java程序来调用系统命令请参看我转载的系列文章:Java调用系统命令学习。最后这个调用交给了ShellCommandExecutor来运行,还是举个例子来说明这个调用形式: