Hadoop中MapReduce运行剖析-Anatomy of a MapReduce Job Run with Hadoop

来源:互联网 发布:远程教学用什么软件 编辑:程序博客网 时间:2024/06/07 11:45

    HINT: 本文是我在查询Hadoop具体运行过程中无意间看到的一篇文章,感觉写的挺详细而且不累赘,便翻译过来.原文作者本身就是Apache组织的一员,所以觉得可信度和准确度应该相对而言较高.才疏学浅,有翻译不对的地方望指正.另附原文地址,想进一步学习的同学可以戳这里Anatomy of a MapReduce Job Run with Hadoop
   

==============================================================================

    你可以用一行简单的代码: JobClient.run(conf)来运行一个MapReduce的Job. 它非常短, 但是隐瞒了很多处理的过程. 这一章的内容将会揭露这些Hadoop运行Job中的步骤.

    整个过程显示在图1中. 从最高级别的视角来看,整个步骤可以分为四个独立的实体(entity).

    1. Client  用于提交MapReduce Job
    2. JobTracker 用于协调整个Job的运行. 其本质上来说是一个Java应用,主类名为 JobTracker.
    3. TaskTracker 用于运行由Job分割成的Tasks. 其本质亦是Java应用,主类名为TaskTracker.
    4. Distributed Filesystem 分布式文件系统. 用于在不同的实体之间共享Job文件.


Figure 1. Hadoop 是怎么运行一个MapReduce Job

    Job Submission (Job提交)

    JobClient中的runJob()方法是一个很方便的方法,它可以创建一个新的JobClient实例并且调用这个新建实例中的submitJob()方法(图1中的step 1). 当提交过一个Job之后, runJob方法会每秒轮询(poll)一下这个Job的运行情况.如果运行情况从上一次报告之后有更新的话,就报告最新的情况给控制台(console). 当任务完成之后,如果此任务成功完成,那么整个job counter(job计数器)都会被显示出来(这里应该指的是每次运行完job之后屏幕给出的那些信息). 否则,造成job失败的错误会被登记在控制台的记录上.

    JobClient的submitJob()方法通过以下步骤来实现job的提交过程:
   1. 向JobTracker申请一个新的Job ID(通过调用JobTracker上的getNewJobID()方法).(图1中的step 2).
   2. 检查此Job的输出详述. 举个例子,如果一个Job的输出目录还没有被定义或者已经存在的话,这个Job就不会被提交,而且会抛出一个异常给MapReduce程序.
   3. 为一个Job计算其输入片. 如果这些输入片无法被计算的话,这里我们假设这个问题是由输入路径不存在导致的,那么这个Job就不会被提交,而且会抛出一个异常给MapReduce程序.
   4. 复制运行此Job所需的资源-包括这个Job的JAR文件,配置文件和计算过的输入片-到此JobTracker的文件系统下的一个用此Job ID命名的目录下.Job的JAR文件会被复制数次,这个次数我们称之为replication factor,由 mapred.submit.replication控制,默认为10. 因此,在job运行的过程中,那些TaskTracker可以从cluster中很多地方获取这个JAR文件(图1中的step 3).
   5. 通过调用JobTracker上的submitJob()来告诉JobTracker此Job已就绪(图1中的step 4).

   Job Initialization(Job初始化)

   当JobTracker接收到一个调用其submitJob()方法的请求时,它会把这个请求放到一个内在队列里. 此队列用于Job调度器挑选和初始化Job. 初始化包含创建一个代表Job运行的对象,其内部封装了此job的tasks和用于跟踪这些tasks的状态和进度的书签信息(图1中的step 5).

   为了创建将要运行的task列表, Job调度器首先检索由JobClient从共享的文件系统中计算出的输入片(图1中的step 6). 然后, 它为每个输入片创建一个map task. 而reduce task的数量则是由JobConf中的mapred.reduce.tasks属性来决定的,此属性可以通过setNumReduceTasks()方法来设置, 然后调度器就只需简单地创建这个数目的reduce tasks就可以了.在这个时候,每个task会被给予它们的ID.

   Task Assignment(Task分配)

   TaskTracker通过一个简单的循环从而周期性的发送心跳信号给JobTracker. 这些心跳信号可以告诉JobTracker这个TaskTracker是处在Alive状态(也就是有效的), 但同时它们也是一个传输信息的通道. 在心跳信息中,一个TaskTracker将会指明它自身是否就绪从而运行一个新的task;如果就绪,则JobTracker将会通过心跳信息中的返回值与其通信并分配给它一个task(图1中的step 7).

   在分配task给TaskTracker之前,JobTracker必须先选择一个job从而选择其task.本章后部分会介绍不同种类的调度算法,但是默认的方法就是一个简单的job优先级.选择一个job之后,JobTracker接着挑选此job中的一个task.

   TaskTracker有固定数目的空位给map task和reduce task: 举个例子,一个tasktracker可能可以同时运行两个map task和两个reduce task.默认的调度器在map task空位和reduce task空位都空闲的情况下,会优先填充map task空位.也就是说,如果一个tasktracker有至少一个空闲的map task空位,jobtracker就会先选择一个map task;否则,它会选择一个reduce task.

   在选择一个reduce task的时候, jobtracker只需简单地从就绪的reduce task 列表中选择下一个就可以(list of yet-to-be-run reduce tasks),因为它不需考虑数据本地性. 但是对于一个map task来说,它需要先考虑tasktracker的网内位置,然后选择一个所需输入跟其相近的任务给这个tasktracker.在最优情况下,此task是data-local性的,即运行此task的结点上有其所需的输入片;其次优先情况下,是rack-local性的,即运行此task的结点和输入片所在的结点在同一个rack上.其他既不是data-local亦不是rack-local性的任务则需要从其他不同的rack上检索所需输入片.你可以从job计数器上分辨这些种类的tasks.

   Task Execution(Task 运行)

   现在,一个tasktracker已经被分配给了一个task,下一步就是运行这个task. 首先,tasktracker从共享的文件系统上复制此job的JAR文件到本地的文件系统上.同时它也复制其他所需的文件(图1中的step 8). 第二步,为这个task创建一个本地的工作目录,同时把JAR包中的内容解压到这个目录下. 第三步,创建一个TaskRunner实例来运行此task.

   TaskRunner创建一个新的Java虚拟机(图1中的step 9)来运行每个task(图1中的step 10),这样任何在用户定义的map和reduce功能中出现的bug将不会影响到tasktracker(举例子来说,将不会导致它崩溃或挂起).所以它可以在tasks中重复利用此JVM.

   子进程通过umbilical 接口来跟其父进程通信. 每隔几秒它会通过此方法来告诉父进程task的运行状况知道task完成.

   Streaming and Pipes(流和管道)

   Streaming和Pipes都运行特定的map和reduce task从而实现用户提供的可实现性目的并且与其交流.



Figure 2. 对于tasktracker和其child来说,Streaming 和 Pipes 的可行性

    在Streaming中, Streaming tasks可以用任何语言写成, 其与进程通过标准输入和输出流来进行通信. 另一方面, Pipes tasks监听一个socket然后传递给C++ 进程一个其环境中的端口号,这样在开始时,此C++进程与其父辈Java Pipes task可以建立一个稳定的稳固的socket通信.

   在这两种情况下,在task的运行过程中,java进程将其输入的key-value对传递给外部进程,外部进程通过用户自定义的map或reduce功能处理此key-value对,然后将输出的key-value对传回给java进程.从tasktracker的角度来看,就好像是一个tasktracker的子进程在运行map/reduce代码.

   Progress and Status Updates(进展和状态更新)

   MapReduce job是长时间运行的批处理任务,持续时间可能是从数分钟到几小时.因为其时间长度很重要,所以用户需要得到此job的运行过程进度.一个job和其tasks都有一个status,此状态包含了job或task的状态(举例来说,即是运行中,成功完成,失败),maps和reduces的运行过程,job计数器的值和一个状态信息.这些状态在job的运行过程中一直在改变,所以,它们怎么能从client得到通信反馈呢?

   当一个task运行中的时候,它会记录它自身的进度,也就是task完成的百分比.对于map tasks来说,其完成进度即是其处理完的输入百分比.对于reduce tasks来说会有一点复杂,但是系统仍然可以估计出其完成处理的输入进度.系统将整个进程分成三个部分,对应shuffle中的三个阶段.举个例子来说,如果task在reducer阶段处理完了其输入的一半,那么则记为此task完成了5/6,因为它已经完成了copy和sort阶段,每个阶段记为1/3,而且还有一半的reducer阶段,共计5/6.

   What Constitutes Progress in MapReduce?(什么构成了MapReduce?)

   进度并不是总是可衡量的,但是它总是可以告诉Hadoop一个task在做一些事情.举个例子来说,一个正在输出记录的task就可以认为是有进度的.虽然它不能表达出已经完成的比例,因为不知道剩下的记录还有多少,即使是创造这些输出的task也不知道.

   进度报告很重要,因为它可以告知Hadoop一个有进度的task并没有fail.下面的所有操作组成了进度:
   1. 读入一个输入记录(mapper/reducer)
   2. 写出一个输出记录(mapper/reducer)
   3. 设置reporter上的状态描述(通过 Reporter的setStatus()方法)
   4. 增加计数器的值(通过Reporter的incrCounter()方法)
   5. 调用Reporter的progress()方法

   Task也有一系列的计数器用来统计在task运行过程中出现的不同的事件,要么就是在框架中内置的那些时间,即map的输出记录数量,要么就是用户自定义的.

   如果一个task报告了其进度,则它树起了一个flag来说明它的状态改变需要告诉tasktracker.这个flag每隔3秒会被一个独立的线程来检验.一旦被树立,则会立即告之tasktracker其当下状态.同时,tasktracker每隔5秒发送心跳信息给jobtracker(这是个最小值,其间隔是与集群的规模有关;对于规模较大的集群来说,间隔会更长一点),另外,tasktracker会把运行在其自身的所有task的状态附带在这个心跳信息中发送出去.计数器信息的发送频率会少一点,长于5秒,因为它们相对来说是会消耗更多的带宽.

   jobtracker把这些更新汇合起来从而形成一个所有job以及其task状态的全局视角观察.最终,像在早前提到的,jobclient每秒轮询一次jobtracker来获得最新的状态.Client亦通过JobClient的getJob()方法来获得一个RunningJob实例,此实例包含了此job的所有状态信息.

   方法调用过程变现在图3中.


Figure 3. 在MapReduce系统中状态更新的广播

   Job Completion(任务完成)

   当jobtracker收到一个信息提示其最后一个task完成之后,其会改变job的状态为"成功".然后,当jobclient轮询状态是,知晓这个job已经成功完成,它就会告之用户这个信息,并且通过runJob()方法来返回值.

   如果被设置过发送HTTP通知,jobtracker亦会发送通知.它可以通过修改job.end.notification.url来实现.

   最后,jobtracker清空此job所有的工作状态,并且告之tasktracker亦清空(比如,删除中间输出).

原创粉丝点击