MAPREDUCE计算流程

来源:互联网 发布:tag在vb什么意思 编辑:程序博客网 时间:2024/05/22 04:32
如果一切都按部就班的进行,那么整个作业的计算流程,应该是作业的提交 -> Map任务的分配和执行 -> Reduce任务的分配和执行-> 作业的完成。而在每个任务的执行中,又包含输入的准备 -> 算法的执行 ->输出的生成,三个子步骤。沿着这个流程,我们可以很快的整理清晰整个Map/Reduce框架下作业的执行。。。

1、作业的提交

一个作业,在提交之前,需要把所有应该配置的东西都配置好,因为一旦提交到了作业服务器上,就陷入了完全自动化的流程,用户除了观望,最多也就能起一个监督作用,惩治一些不好好工作的任务。。。
基本上,用户在提交代码阶段,需要做的工作主要是这样的:
首先,书写好所有自定的代码,最起码,需要有Map和Reduce的执行代码。在Hadoop中,Map需要派生自Mapper<K1, V1,K2, V2>接口,Reduce需要派生自Reducer<K2, V2, K3,V3>接口。这里都是用的泛型,用以支持不同的键值类型。这两个接口都仅有一个方法,一个是map,一个是reduce,这两个方法都直接受四个参数,前两个是输入的相关的数据结构,第三个是作为输出相关的数据结构,最后一个,是一个Reporter类的实例,实现的时候可以利用它来统计一些计数。除了这两个接口,还有大量可以派生的接口,比如分割的Partitioner<K2,V2>接口。。。
然后,需要书写好主函数的代码,其中最主要的内容就是实例化一个JobConf类的对象,然后调用其丰富的setXXX接口,设定好所需的内容,包括输入输出的文件路径,Map和Reduce的类,甚至包括读取写入文件所需的格式支持类,等等。。。
最后,调用JobClientrunJob方法,提交此JobConf对象。runJob方法会先行调用到JobSubmissionProtocol接口所定义的submitJob方法,将此作业,提交给作业服务器。接着,runJob开始循环,不停的调用JobSubmissionProtocol的getTaskCompletionEvents方法,获得TaskCompletionEvent类的对象实例,了解此作业各任务的执行状况。。。

2、Map任务的分配

当一个作业提交到了作业服务器上,作业服务器会生成若干个Map任务,每一个Map任务,负责将一部分的输入转换成格式与最终格式相同的中间文件。通常一个作业的输入都是基于分布式文件系统的文件(当然在单机环境下,文件系统单机的也可以...),因为,它可以很天然的和分布式的计算产生联系。而对于一个Map任务而言,它的输入往往是输入文件的一个数据块,或者是数据块的一部分,但通常,不跨数据块。因为,一旦跨了数据块,就可能涉及到多个服务器,带来了不必要的复杂性。。。
当一个作业,从客户端提交到了作业服务器上,作业服务器会生成一个JobInProgress对象,作为与之对应的标识,用于管理。作业被拆分成若干个Map任务后,会预先挂在作业服务器上的任务服务器拓扑树。这是依照分布式文件数据块的位置来划分的,比如一个Map任务需要用某个数据块,这个数据块有三份备份,那么,在这三台服务器上都会挂上此任务,可以视为是一个预分配。。。
关于任务管理和分配的大部分的真实功能和逻辑的实现,JobInProgress则依托JobInProgressListenerTaskScheduler的子类。TaskScheduler,顾名思义是用于任务分配的策略类(为了简化描述,用它代指所有TaskScheduler的子类...)。它会掌握好所有作业的任务信息,其assignTasks函数,接受一个TaskTrackerStatus作为参数,依照此任务服务器的状态和现有的任务状况,为其分配新的任务。而为了掌握所有作业相关任务的状况,TaskScheduler会将若干个JobInProgressListener注册到JobTracker中去,当有新的作业到达、移除或更新的时候,JobTracker会告知给所有的JobInProgressListener,以便它们做出相应的处理。。。
任务分配是一个重要的环节,所谓任务分配,就是将合适作业的合适任务分配到合适的服务器上。不难看出,里面蕴含了两个步骤,先是选择作业,然后是在此作业中选择任务。和所有分配工作一样,任务分配也是一个复杂的活。不良好的任务分配,可能会导致网络流量增加、某些任务服务器负载过重效率下降,等等。不仅如此,任务分配还是一个无一致模式的问题,不同的业务背景,可能需要不同的算法才能满足需求。因此,在Hadoop中,有很多TaskScheduler的子类,像Facebook,Yahoo,都为其贡献出了自家用的算法。在Hadoop中,默认的任务分配器,是JobQueueTaskScheduler类。它选择作业的基本次序是:MapClean Up Task(Map任务服务器的清理任务,用于清理相关的过期的文件和环境...) -> Map SetupTask(Map任务服务器的安装任务,负责配置好相关的环境...) -> Map Tasks -> Reduce Clean Up Task-> Reduce Setup Task -> ReduceTasks。在这个前提下,具体到Map任务的分配上来。当一个任务服务器工作的游刃有余,期待获得新的任务的时候,JobQueueTaskScheduler会按照各个作业的优先级,从最高优先级的作业开始分配。每分配一个,还会为其留出余量,已被不时之需。举一个例子:系统目前有优先级3、2、1的三个作业,每个作业都有一个可分配的Map任务,一个任务服务器来申请新的任务,它还有能力承载3个任务的执行,JobQueueTaskScheduler会先从优先级3的作业上取一个任务分配给它,然后再留出一个1任务的余量。此时,系统只能在将优先级2作业的任务分配给此服务器,而不能分配优先级1的任务。这样的策略,基本思路就是一切为高优先级的作业服务,优先分配不说,分配了好保留有余力以备不时之需,如此优待,足以让高优先级的作业喜极而泣,让低优先级的作业感慨既生瑜何生亮,甚至是活活饿死。。。
确定了从哪个作业提取任务后,具体的分配算法,经过一系列的调用,最后实际是由JobInProgressfindNewMapTask函数完成的。它的算法很简单,就是尽全力为此服务器非配且尽可能好的分配任务,也就是说,只要还有可分配的任务,就一定会分给它,而不考虑后来者。作业服务器会从离它最近的服务器开始,看上面是否还挂着未分配的任务(预分配上的),从近到远,如果所有的任务都分配了,那么看有没有开启多次执行,如果开启,考虑把未完成的任务再分配一次(后面有地方详述...)。。。
对于作业服务器来说,把一个任务分配出去了,并不意味着它就彻底解放,可以对此任务可以不管不顾了。因为任务可以在任务服务器上执行失败,可能执行缓慢,这都需要作业服务器帮助它们再来一次。因此在Task中,记录有一个TaskAttemptID,对于任务服务器而言,它们每次跑的,其实都只是一个Attempt而已,Reduce任务只需要采信一个的输出,其他都算白忙乎了。。。

3、Map任务的执行

与HDFS类似,任务服务器是通过心跳消息,向作业服务器汇报此时此刻其上各个任务执行的状况,并向作业服务器申请新的任务的。具体实现,是TaskTracker调用InterTrackerProtocol协议的heartbeat方法来做的。这个方法接受一个TaskTrackerStatus对象作为参数,它描述了此时此任务服务器的状态。当其有余力接受新的任务的时候,它还会传入acceptNewTasks为true的参数,表示希望作业服务器委以重任。JobTracker接收到相关的参数后,经过处理,会返回一个HeartbeatResponse对象。这个对象中,定义了一组TaskTrackerAction,用于指导任务服务器进行下一步的工作。系统中已定义的了一堆其TaskTrackerAction的子类,有的对携带的参数进行了扩充,有的只是标明了下ID,具体不详写了,一看便知。。。
当TaskTracker收到的TaskTrackerAction中,包含了LaunchTaskAction,它会开始执行所分配的新的任务。在TaskTracker中,有一个TaskTracker.TaskLauncher线程(确切的说是两个,一个等Map任务,一个等Reduce任务),它们在痴痴的守候着新任务的来到。一旦等到了,会最终调用到Task的createRunner方法,构造出一个TaskRunner对象,新建一个线程来执行。对于一个Map任务,它对应的Runner是TaskRunner的子类MapTaskRunner,不过,核心部分都在TaskRunner的实现内。TaskRunner会先将所需的文件全部下载并拆包好,并记录到一个全局缓存中,这是一个全局的目录,可以供所有此作业的所有任务使用。它会用一些软链接,将一些文件名链接到这个缓存中来。然后,根据不同的参数,配置出一个JVM执行的环境,这个环境与JvmEnv类的对象对应。
接着,TaskRunner会调用JvmManagerlaunchJvm方法,提交给JvmManager处理。JvmManager用于管理该TaskTracker上所有运行的Task子进程。在目前的实现中,尝试的是池化的方式。有若干个固定的槽,如果槽没有满,那么就启动新的子进程,否则,就寻找idle的进程,如果是同Job的直接放进去,否则杀死这个进程,用一个新的进程代替。每一个进程都是由JvmRunner来管理的,它也是位于单独线程中的。但是从实现上看,这个机制好像没有部署开,子进程是死循环等待,而不会阻塞在父进程的相关线程上,父线程的变量一直都没有个调整,一旦分配,始终都处在繁忙的状况了。
真实的执行载体,是Child,它包含一个main函数,进程执行,会将相关参数传进来,它会拆解这些参数,并且构造出相关的Task实例,调用其run函数进行执行。每一个子进程,可以执行指定个数量的Task,这就是上面所说的池化的配置。但是,这套机制在我看来,并没有运行起来,每个进程其实都没有机会不死而执行新的任务,只是傻傻的等待进程池满,而被一刀毙命。也许是我老眼昏花,没看出其中实现的端倪。。。

4、Reduce任务的分配与执行

比之Map任务,Reduce的分配及其简单,基本上是所有Map任务完成了,有空闲的任务服务器,来了就给分配一个Job任务。因为Map任务的结果星罗棋布,且变化多端,真要搞一个全局优化的算法,绝对是得不偿失。而Reduce任务的执行进程的构造和分配流程,与Map基本完全的一致,没有啥可说的了。。。
但其实,Reduce任务与Map任务的最大不同,是Map任务的文件都在本地隔着,而Reduce任务需要到处采集。这个流程是作业服务器经由此Reduce任务所处的任务服务器,告诉Reduce任务正在执行的进程,它需要的Map任务执行过的服务器地址,此Reduce任务服务器会于原Map任务服务器联系(当然本地就免了...),通过FTP服务,下载过来。这个隐含的直接数据联系,就是执行Reduce任务与执行Map任务最大的不同了。。。

5、作业的完成

当所有Reduce任务都完成了,所需数据都写到了分布式文件系统上,整个作业才正式完成了。此中,涉及到很多的类,很多的文件,很多的服务器,所以说起来很费劲,话说,一图解千语,说了那么多,我还是画两幅图,彻底表达一下吧。。。
首先,是一个时序图。它模拟了一个由3个Map任务和1个Reduce任务构成的作业执行流程。我们可以看到,在执行的过程中,只要有人太慢,或者失败,就会增加一次尝试,以此换取最快的执行总时间。一旦所有Map任务完成,Reduce开始运作(其实,不一定要这样的...),对于每一个Map任务来说,只有执行到Reduce任务把它上面的数据下载完成,才算成功,否则,都是失败,需要重新进行尝试。。。
而第二副图,不是我画的,就不转载了,参见这里,它描述了整个Map/Reduce的服务器状况图,包括整体流程、所处服务器进程、输入输出等,看清楚这幅图,对Map/Reduce的基本流程应该能完全跑通了。有这几点,可能图中描述的不够清晰需要提及一下,一个是在HDFS中,其实还有日志文件,图中没有标明;另一个是步骤5,其实是由TaskTracker主动去拉取而不是JobTracker推送过来的;还有步骤8和步骤11,创建出来的MapTask和ReduceTask,在Hadoop中都是运行在独立的进程上的。。。

====================================================================
今天看到关于MAPREDUCE的三篇文章,觉得不错,贴上来。
http://www.cnblogs.com/spork/archive/2010/01/11/1644342.html
http://www.cnblogs.com/spork/archive/2010/01/11/1644346.html
http://www.cnblogs.com/spork/archive/2010/01/11/1644350.html
原创粉丝点击