hadoop作业调度详解之TaskTracker

6. TaskTracker
       TaskTracker是在网络环境中开始和跟踪任务的核心位置。与Jobtracker连接请求执行任务而后报告任务状态
6.0 TaskTracker的启动
        1. 与JobTracker一样,里面包含一个main()方法,在hadoop启动的时候启动此进程。
        Main()方法最主要的一句话  TaskTracker(conf).run();
        TaskTracker(conf)获取本机的一些配置信息,初始化服务器并启动服务器(StatusHttpServer);然后调用initialize(),这个方法才是真正构造TaskTracker的地方,把它作为一个单独的方法便可以再次调用并可以在close()之后回收对象,就是初始化一些变量对 象,最后启动线程:
        taskMemoryManager为TaskMemoryManagerThread类的对象。管理本机上task运行时内存的使用,杀死任何溢出和超出内存限制的task-trees。
        mapLauncher与reduceLauncher都是TaskLauncher类的对象,其作用是启动maptask和reducetask任务线程。根据tasksToLaunch判断是否需要新建任务,其中的调用的关系为:run()→startNewTask()→localizeJob()→launchTaskFor→JoblaunchTask() →localizeTask。
       2.run()方法中启动TaskTracker服务器然后一直循环。循环会尝试连接到的JobTracker。主要调用了两个方法startCleanupThreads(),offerService()。
       startCleanupThreads()启动为守护进程,可以用来删除一个独立线程的路径。
       offerService()类似于JobTracker中的offerService()方法,即服务器执行的主循环。规定的时间内给JobTracker发送心跳信息,并处理返回的命令。

下面具体介绍流程中的每一步。
6.1 TaskTracker加载Task到子进程
       Task的执行实际是由TaskTracker发起的,TaskTracker会定期与JobTracker进行一次通信,报告自己Task的执行状态,接收JobTracker的指令等。如果发现有自己需要执行的新任务也会在这时启动,即是在TaskTracker调用JobTracker的heartbeat()方法时进行,此调用底层是通过IPC层调用Proxy接口实现。
       1.TaskTracker.run() 连接JobTracker
       TaskTracker的启动过程会初始化一系列参数和服务,然后尝试连接JobTracker(即必须实现InterTrackerProtocol接口),如果连接断开,则会循环尝试连接JobTracker,并重新初始化所有成员和参数。
       2.TaskTracker.offerService() 主循环
       如果连接JobTracker服务成功,TaskTracker就会调用offerService()函数进入主执行循环中。这个循环会每隔10秒与JobTracker通讯一次,调用transmitHeartBeat(),获得HeartbeatResponse信息。然后调用HeartbeatResponse的getActions()函数获得 JobTracker传过来的所有指令即一个TaskTrackerAction数组。再遍历这个数组,如果是一个新任务指令是LaunchTaskAction则调用调用addToTaskQueue加入到待执行队列,如果为commitTaskAction则否则加入到commitResponses。否则tasksToCleanup队列,交给一个 taskCleanupThread线程来处理,如执行KillJobAction 或者KillTaskAction等。
       3.TaskTracker.transmitHeartBeat() 获取JobTracker指令
       在transmitHeartBeat()函数处理中,TaskTracker会创建一个新的TaskTrackerStatus对象记录目前任务的执行状况,检查目前执行的Task数目以及本地磁盘的空间使用情况等,如果可以接收新的Task则设置heartbeat()的askForNewTask参数为true。然后通过IP C 接口调用JobTracker的heartbeat()方法发送过去,heartbeat()返回值TaskTrackerAction数组。

      4.TaskTracker.addToTaskQueue,交给TaskLauncher处理
      TaskLauncher是用来处理新任务的线程类,包含了一个待运行任务的队列 tasksToLaunch。
             1.根据action中的任务类型即是MapTask还是ReduceTask调用相应的TaskLanucher的addToTaskQueue添加action到task队列中。
             2.TaskTracker.addToTaskQueue会调用TaskTracker的registerTask,创建TaskInProgress对象来调度和监控任务,并把它加入到runningTasks队列中。同时将这个TaskInProgress加到tasksToLaunch 中,并notifyAll()唤醒一个线程运行,该线程从队列   tasksToLaunch取出一个待运行任务,调用TaskTracker的startNewTask运行任务。
      5.TaskTracker.startNewTask() 启动新任务
      调用localizeJob()真正初始化Task并开始执行。
      6.TaskTracker.localizeJob() 初始化job目录等
      此函数主要任务是初始化工作目录workDir,再将job jar包从HDFS复制到本地文件系统中,调用RunJar.unJar()将包解压到工作目录。然后创建一个RunningJob并调用addTaskToJob()函数将它添加到runningJobs监控队列中。addTaskToJob方法把一个任务加入到该 任务属于的runningJob的tasks列表中。如果该任务属于的runningJob不存在,先新建,加到runningJobs中。完成后即调用launchTaskForJob()开始执行Task。
       7.TaskTracker.launchTaskForJob() 执行任务
       启动Task的工作实际是调用TaskTracker$TaskInProgress的launchTask()函数来执行的。
       8.TaskTracker$TaskInProgress.launchTask() 执行任务
       执行任务前先调用localizeTask()更新一下jobConf文件并写入到本地目录中。然后通过调用Task的createRunner()方法创建TaskRunner对象并调用其start()方法最后启动Task独立的java执行子进程。
       9.Task.createRunner() 创建启动Runner对象
       Task有两个实现版本,即MapTask和ReduceTask,它们分别用于创建Map和Reduce任务。MapTask会创建MapTaskRunner来启动Task子进程,而ReduceTask则创建ReduceTaskRunner来启动。
      10.TaskRunner.start() 启动子进程
                1.TaskRunner负责将一个任务放到一个进程里面来执行。它会调用run()函数来处理,主要的工作就是初始化启动java子进程的一系列环境变量,包括设定工作目录workDir,设置CLASSPATH环境变量等。然后装载job jar包。
                2.在run中通过jvmManager.launchJvm(TaskRunner,JvmManager.constructJvmEnv(setup,vargs,stdout,stderr,logSize,workDir, env, pidFile, conf))方法管理该TaskTracker上所有运行的Task子进程。每一个进程都是由JvmRunner来  管理的, 它也是位于单独线程中的。JvmManager的launchJvm方法启动一个jvm。根据任务是map还是reduce,生成对应的JvmRunner并放到对应JvmManagerForType的进程容器中进行管理。JvmManagerForType的reapJvm()为一个任务启动一个JVM。
                3.分配一个新的JVM进程。如果JvmManagerForType槽满,就寻找idle的进程,如果是同Job的直接放进去,否则杀死这个进程,用一个新的进程代替。如果槽没有满,那么就启动新的子进程。生成新的进程使用spawnNewJvm方法。spawnNewJvm使用JvmRunner线程的run  方法,run方法用于生成一个新的进程并运行它,具体实现是调用runChild.
                4.在执行即启动一个jvm即运行一个子进程。Child类。

6.3 子进程child执行MapTask
       0.真实的执行载体,是Child,它包含一个 main函数,进程执行,会将相关参数传进来,它会拆解这些参数,通过getTask(jvmId)向父进程索取任务,并且构造出相关的Task实例,然后使用Task的run()启动任务。
       1.run
       方法相当简单,配置完系统的TaskReporter后,就根据情况执行runJobCleanupTask,runJobSetupTask,runTaskCleanupTask或执行map。
       2. 执行map即runNewMapper(job, split, umbilical, reporter)
                1.获得TaskAttemptContext的对象taskcontext用于获得其他相关信息,通过气获得mapper类对象、设置输入格式inputformat、重建输入分片split、构建RecordReader
               2.构造Mapper的输出即output,目的是通过output收集map的结果。通过RecordWrite进行的,也分两种情况,如果没有Reducer用NewDirectMapOutputCollector,否则用NewOutputCollector
               这是在新的API下的。而新的API下面真正作用还是老API即MapOutputCollector其有两个子类:MapOutputBuffer和DirectMapOutputCollector。 DirectMapOutputCollector用在不需要Reduce阶段的时候。如果Mapper后续有reduce任务,系统会使用   MapOutputBuffer做为输出, 
              3.根据创建好的以上信息创建maper的context
              4.最后mapper.run(Context)执行map
       3.Mapper的run(Context)
       会先创建对应的key,value对象,然后,对InputSplit的每一对<key,value>,调用用户实现的Mapper接口实现类的map方法,每处理一个数据对,就要使用OutputCollector收集每次处理kv对后得到的新的kv对,把他们spill到文件或者放到内存,以做进一步的处 理,比如排序,combine等。
       4.NewOutputCollector即output等同于context
                1.context.write(key,value)
                通过NewOutputCollector的收集每次调用map后得到的新的kv对,并把他们spill到文件或者放到内存,以做进一步的处理,比如排序,combine等。而其中实际操作的是MapOutputBuffer对象collector进行结果的收集。collector.collect(key,    value,partitioner.getPartition(key, value, partitions));MapOutputBuffer使用了一个缓冲区对map的处理结果进行缓存,放在内存中,又使用几个数组对这个缓冲区进行管理。在创建这个对象是判断是否有combiner有的话实例化一个    CombinerRunner。根据这个对象是否被实例化在下面的操作中决定是否执行combiner。
               2.在适当的时机,缓冲区中的数据会被spill到硬盘中。
               向硬盘中写数据的时机:
             (1)当内存缓冲区不能容下一个太大的k v对时。spillSingleRecord方法。
             (2)内存缓冲区已满时。SpillThread线程。这是MapOutputBuffer的内部类。
             (3)Mapper的结果都已经collect了,需要对缓冲区做最后的清理。Flush方法。
       5.MapOutputBuffer中combiner说明:
               0.有关combiner的类是在Task中实现的是Task的内部类MapOutputBuffer的内部类,主要有:CombinerRunner其又有两个子类NewCombinerRunner及OldCombinerRunner,这个类是combiner的实现处;另一个是实现了OutputCollector的CombinerOutputCollector  用于收集Combiner的输出。
              1.创建,通过静态方法CombinerRunner.create(job, getTaskID(), combineInputCounter,reporter, null)创建一个CombinerRunner的对象。如Job中设置了Combiner则进行创建NewCombinerRunner或是OldCombinerRunner。或者返回一个null
              2.如果创建了即非空则创建CombinerOutputCollector对象用于收集结果。其内有一个计数器记录收集到的结果个数。
      6.spillThread线程:将缓冲区中的数据spill到硬盘中。
             1.需要spill时调用函数sortAndSpill,按照partition和key做排序。默认使用的是快速排序QuickSort。如果没有combiner,则直接将记录写入到相应的分区中,否则,调用CombinerRunner的combine,先做combiner。然后输出。
             2.有combiner时处理时,首先为outputcollector设置write用于写文件。然后创建一个用于迭代map结果的Iterator。
             3.combinerRunner.combine(kvIter, combineCollector)执行combiner
             4.在NewCombinerRunner的combiner方法中通过反射获得reduce类对象,Combiner是继承与Reduce的。创建用于收集reduce结果的Reduce.Context.
            5.reducer.run(reducerContext)开始真正执行combiner就是一个reduce任务。 

6.4 子进程执行ReduceTask
      0.ReduceTask.run方法开始和MapTask类似,包括initialize()初始化,根据情况看是否调用runJobCleanupTask(),runJobSetupTask(),runTaskCleanupTask()。之后进入正式的工作,主要有这么三个步骤:Copy、Sort、Reduce。
      1. Copy
      就是从执行各个Map任务的服务器那里,收到map的输出文件。拷贝的任务,是由ReduceTask.ReduceCopier 类来负责。通过ReduceCopier的fetchOutputs()方法取得map的结果
     流程: 使用ReduceCopier.fetchOutputs开始
   (1)索取任务。使用GetMapEventsThread线程。该线程的run方法不停的调用getMapCompletionEvents方法,该方法又使用RPC调用TaskUmbilicalProtocol协议的getMapCompletionEvents,方法使用所属的jobID向其父TaskTracker询问此作业个Map任务  的完成状况(TaskTracker要向JobTracker询问后再转告给它...)。返回一个数组TaskCompletionEvent events[]。TaskCompletionEvent包含taskid和ip地址之类的信息。

   (2)当获取到相关Map任务执行服务器的信息后,有一个线程MapOutputCopier开启,做具体的拷贝工作。它会在一个单独的线程内,负责某个Map任务服务器上文件的拷贝工作。MapOutputCopier的run循环调用copyOutput,copyOutput又调用    getMapOutput,使用HTTP远程拷贝。

   (3)getMapOutput远程拷贝过来的内容(当然也可以是本地了...),作为MapOutput对象存在,它可以在内存中也可以序列化在磁盘上,这个根据内存使用状况来自动调节。

   (4)同时合并,还有一个内存Merger线程InMemFSMergeThread和一个文件Merger线程LocalFSMerger在同步工作,它们将下载过来的文件(可能在内存中,简单的统称为文件...),做着归并排序,以此,节约时间,降低输入文件的数量,为后续的排序工作减   负。InMemFSMergeThread的run循环调用doInMemMerge,该方法使用工具类Merger实现归并,如果需要combine,则combinerRunner.combine。

 2.Sort(其实相当于合并)

       排序工作,就相当于上述排序工作的一个延续。它会在所有的文件都拷贝完毕后进行。使用工具类Merger归并所有的文件。经过这一个流程,一个合并了所有所需Map任务输出文件的新文件产生了。而那些从其他各个服务器网罗过来的 Map任务输出文件,全部删除了。

 3.Reduce 
       1.Reduce任务的最后一个阶段。它会准备好Map的 keyClass("mapred.output.key.class"或"mapred.mapoutput.key.class"), valueClass("mapred.mapoutput.value.class"或"mapred.output.value.class")和 Comparator (“mapred.output.value.groupfn.class”或 “mapred.output.key.comparator.class”)。
       2.根据参数useNewAPI判断执行runNewReduce还是runOldReduce。分析润runNewReduce
       3.runNewReducer
                0.像报告进程书写一些信息
                1.获得一个TaskAttemptContext对象。通过这个对象创建reduce、output及用于跟踪的统计output的RecordWrit、最后创建用于收集reduce结果的Context
                2.reducer.run(reducerContext)开始执行reduce

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值