TaskTracker LaunchTask过程与CleanTask过程

UHP博客文章地址:http://yuntai.1kapp.com/?p=525

LaunchTask执行流程

TaskTracker.run()->offerService()

TaskTracker实现了Runnable接口,他的run函数的主要逻辑在offerService函数里,是一个循环,保持和JobTracker的心跳通信,如果TaskTracker当前有空余的Task槽位(Task的最大槽位数由TaskTracker上的配置文件中的mapred.tasktracker.map.tasks.maximum及mapred.tasktracker.reduce.tasks.maximum来决定的,在mapred-site.xml中配置),则在心跳消息里附带上Task任务申请信息(心跳消息的封装在 transmitHeartBeat()函数里),如果JobTracker收到此类心跳,且有未分配的Task,则会将适当的Task(这里在JobTracker端有一个Task调度的过程)通过心跳响应回给TaskTracker,

 

TaskTrackerAction[] actions = heartbeatResponse.getActions();

action有以下几类: LAUNCH_TASK,KILL_TASK,KILL_JOB, REINIT_TRACKER,COMMIT_TASK

actions是TaskTracker收到响应后根据对应的类型创建的,并不是JobTracker直接传过来的;

 

正常情况下,收到的第一个action都是LaunchTaskAction,根据任务类型,将该action放到对应的TaskLauncher中去,假设当前是一个Map Task,则mapLauncher.addToTaskQueue(action);这个函数里会生成一个TaskInProgresstip,将tip放置到任务启动队列:ListtasksToLaunch,并通知其他线程(实际是TaskLaucher这个线程)有任务需要处理,tasksToLaunch.notifyAll(),这将导致run函数里的等待返回;

TaskLauncher.run()->startNewTask()

TaskLaucher本身是一个线程,他的run函数不断从tasksToLaunch里获取任务进行处理,如果没有,则等待,流程如下:

  1. 取tasksToLaunch里的任务,没有,则等待,有则取出来进行处理;
  2. 判断当前TaskTracker是否有空闲的任务槽位,没有,则等待;
  3. 如果有空闲槽位,则startNewTask(tip) 开始处理这个任务;

 

startNewTask的主要逻辑位于localizeJob函数里,他所做的工作如下:

 

  1. 将要处理的任务放到所属job里(当然,对于Job的第一个task而言,需要新创建一个RunningJob实例,并放置到RunningJob Map里);

 

  1. 创建Job相关的工作路径(jobcache路径以及当前job的子路径:包括job路径,即以jobID命名的路径,用来存放job运行所需资源,如job.xml、Job的work路径、存放jar包的路径)并下载资源;

具体举例看一下这些路径的关系:

假设当前taskracker的目录:/home/xxx/hadoop/tmp/mapred/local/taskTracker

下面的/home/xxx/hadoop/tmp/mapred/local用~代替

则有jobcache目录 : ~/taskTracker/jobcache;

当前job路径: ~/taskTracker/jobcache/job_201202101537_0001 (job.xml下载到这里)

jar包路径 : ~/taskTracker/jobcache/job_201202101537_0001/jars (jar包下载并解压缩到这里)

work路径 :~/taskTracker/jobcache/job_201202101537_0001/work

 

  1. 重新生成job.xml(因为在这里可能新添加了一些配置信息,这些配置信息需要应用到后面的Child JVM中),解压缩job.jar;

 

  1. 启动任务launchTaskForJob,实际是调用里TaskInProgress的launchTask()。
  2. localizeTask(),做一些与该任务相关的本地化工作,包括创建任务路径,添加路径配置信息,并生成新的配置文件;

TaskInProgress. launchTask()

当前task路径:

~/taskTracker/jobcache/job_201202101537_0001/attempt_201202101537_0001_m_000002_0

当前task的work路径 :

~/taskTracker/jobcache/job_201202101537_0001/attempt_201202101537_0001_m_000002_0/work

这里还生成与该task相关的job.xml,放到当前task路径下

 

  1. 根据task类型创建TaskRunner,并开始TaskRunner线程运行。

TaskRunner.run()

TaskRunner继承Thread类,本身是一个线程类,在他的run函数里真正开始启动任务执行进程,主要执行步骤如下:

 

  1. 创建任务执行的工作路径,这个工作路径实际是真正执行MapR任务的Child JVM的工作路径,会在启动子进程的时候传递给ProcessBuilder;
  2. 拷贝分布式缓存中的资源到本地(有些业务处理会使用一些共用资源,为了避免手动的把这些资源拷到各个TaskTracker上,可以使用分布式缓存,jobclient将这些资源拷到分布式缓存,各个task再将这些资源本地化,可能涉及到资源文件以及一些jar包)这些文件拷贝到~/archive下;
  3. 拷贝完后,更新当前task目录下的job.xml(因为加入一些新的路径配置信息);
  4. 然后是将当前job.jar路径添加到classPath,把分布式缓存中的jar包和class文件路径也添加到classpath(如果有的话);将当前task的work路径添加到classpath;添加子进程的JVM运行参数;设置LD_LIBRARAY_PATH;生成task的tmp路径;添加其他参数;生成pid文件.几个比较重要的参数需要传给Child进程:taskTracker的RPC服务器地址,当前的taskId;
  5. 把当前task的信息添加到内存管理器;重定向子进程的输入输出log;
  6. 最后开始启动task运行子进程,launchJVMAndWait()。

launchJVMAndWait()->JVMManager. launchJVM()->JVMManagerForType. reapJVM()

子JVM生成规则如下:

//Check whether there is a free slot to start a new JVM.

//,or, Kill a (idle) JVM and launch a new one

//When this method is called, we *must*

// (1) spawn a new JVM (if we are below the max)

未达到上限,新建

// (2) find an idle JVM (that belongs to the same job), or,

重用同job的空闲且运行的任务数还没达到最大JVM(一个子JVM可运行多个任务,最大任务数由jobtracker上mapred-site.xml里的mapred.job.reuse.JVM.num.tasks配置,注意:子JVM并不是并行运行多个任务的,他是串行的去taskTracker上取可运行的任务的)

// (3) kill an idle JVM (from a different job)

kill不同job的空闲JVM后新建(实际上相同job的JVM也会被kill)

// (the order of return is in the order above)

 

//Cases when a JVM is killed:

// (1) the JVM under consideration belongs to the same job

//     (passed in the argument). In this case, kill only when

//     the JVM ran all the tasks it was scheduled to run (in terms

//     of count).

相同job,kill已执行完所有task的JVM

// (2) the JVM under consideration belongs to a different job and is

//     currently not busy

不同job,kill空闲的JVM

//But in both the above cases, we see if we can assign the current

//task to an idle JVM (hence we continue the loop even on a match)

 

重用调用setRunningTaskForJVM(JVMRunner.JVMId, t)将重用的JVM id和task建立映射后返回。

 

除重用的情况,新建和kill掉新建需要执行spawnNewJVM(),会新建一个JVMRunner并启动。也会调setRunningTaskForJVM(),此外还会调JVMIdToRunner.put(JVMRunner.JVMId, JVMRunner)将JVM id和JVMRunner对象建立映射。

JVMRunner.run()->runChild()

主要调用DefaultTaskController. launchTask(),会在finally中调用kill()和updateOnJVMExit(JVMId, exitCode)。

DefaultTaskController. launchTask()

将子JVM的所有环境信息写入相应path下的sh脚本,通过runtime执行该脚本启动Child进程并等待获得返回值。

Child.main()

从脚本传入的参数生成相关task配置信息和工作目录,调用TaskTracker.getTask(),通过JVMid向JVMManager获取task具体信息(包含在JVMTask中),并将进程id在JVMManager中和JVMid建立映射。getTask()时如果JVMManager的JVMIdToRunner或TaskTracker的runningJobs或JVMManager.getTaskForJVM不包含此JVMid或此id相应对象,将返回包含空task信息的JVMTask。

CleanTask执行流程

TaskTracker.run()->offerService()

TaskTrackerAction[] actions = heartbeatResponse.getActions();

……

tasksToCleanup.put(action);

获取到KILL_TASK类型的action,放入tasksToCleanup。

taskCleanupThread.run()->TaskTracker .processKillTaskAction-> TaskTracker. purgeTask()

taskCleanupThread一直循环着处理KillJobAction和KillTaskAction.

purgeTask()中将指定task从相应job中移除,再调用tip.jobHasFinished

TaskInProgress. jobHasFinished()

对特定状态的task进行kill。

对所有task进行cleanup操作。

TaskInProgress. kill()

对特定task的TaskRunner进行kill。

设置所有task的完成时间,从MemoryManager中移除task,释放slot。

TaskRunner.kill()->JVMManager. taskKilled()->JVMManager. killJVM-> JVMManager. killJVMRunner-> JVMRunner.kill()

根据JVMIdToPid查找相应JVMid的pid,找到,通过kill命令进行kill;否则无操作。

 

潜在问题

子JVM失去控制

如果同一Task的launch和clean操作间隔很短,可能会出现TaskTracker对Child进程生命周期失去控制的情况。比如Child进程已启动,但还未执行到getTask()从而还没有向TaskTracker报告其进程id,而在此之前TaskTracker已收到kill消息,进行了kill操作。此时kill操作实际上什么也没做(因为没有pid来执行kill命令),而且还清除了jvmid等相关信息,完全放弃了对Child进程的控制。一旦Child因为某些原因未能正常结束而是阻塞,就会出现Child进程数量超过配置的情况。而且随着Child进程数量的增加,系统资源会越来越少最终不可用(jhdfs压力测试就出现了这种情况)。考虑可以通过脚本的形式定期对Child进程进行检验,kill掉阻塞的Child。

 

参考链接:

http://blog.csdn.net/zhangxinfa/article/details/7477501

原创文章,转载请注明出处:http://blog.csdn.net/wind5shy/article/details/8283534


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值