Hadoop1中Task运行过程

来源:互联网 发布:c语言爬虫 编辑:程序博客网 时间:2024/05/18 22:43
当我们编写一个Mapreduce的作业时候,只需要实现map()和reduce()两个函数就可以。

其中map阶段大概可以划分 read 、map、collect、spill和combine五个阶段 。reduce阶段可以划分shuffle、merge、sort、reduce和write五个阶段。
一个应用程序被划分成map和reduce两个计算阶段,它们分别有一个或者多个map task或者reduce task组成。其中,每个map task 处理输入数据集合中的一片数据(InputSplit),并将产生的若干个数据片段写到本地磁盘上,而reduce task则从每个map task 上远程拷贝相应的数据片段,经分组聚集和归约后,将结果写入到hdfs上作为最终结果,总体来看,map task与 reduce task之间的数据采用了pull模型。为了能够容错,map task将中间计算结果存放到本地磁盘中,而reduce task则通过http请求从各个map task端拖取相应的输入数据。为了更好的支持大量reduce task并发从map task 端拷贝数据,hadoop采用了jetty server作为http server处理并发数据读请求。
这里写图片描述
对于map task而言,它的执行过程概述为:首先,通过用户提交的inputformat将对应的inputsplit解析成一系列key/value,并依此交给用户编写的map()函数处理;接着按照指定的partitioner对数据分片,以确定每个key/value将交给哪个reduce task处理;之后将数据交给用户定义的combiner进行一次本地规约(如果用户没有定义直接跳过);最后将处理结果保存到本地磁盘上。
对于reduce task而言,由于它的输入数据来自各个map task,因此首先需要通过http 请求从各个已经运行完成的map task上拷贝对应的数据分片,待所有数据拷贝完成后,在以key为关键字对所有数据进行排序,通过排序,key相同的记录聚集到一起形成若干分组,然后将每组数据交给用户编写的reduce()函数处理,并将数据结果直接写到hdfs上作为最终输出结果。

IFile存储格式
考虑到map task的输出文件需要写到本地磁盘上并被reduce task远程拷贝,为尽可能减少数据量以避免不必要的磁盘和网络开销,hadoop内部实现了支持行压缩的数据存储格式:IFile。
Map task中间输出结果和reduce task远程拷贝结果被存放在IFile格式的磁盘文件或者内存中。为了尽可能减少map task写入磁盘数据量和跨网络传输数据量,IFile支持按行压缩数据记录,当前hadoop提供了Zlib(默认压缩方式)、BZip2等压缩算法。如果用户想启用数据压缩功能,则需为作业添加以下两个配置选项。
mapred.compress.map.output:是否支持中间输出结果压缩,默认是false。
mapred.map.output.compression.codec:压缩器(默认是基于Zlib算法的压缩器DefaultCodec)。任何一个压缩器需要实现CompressionCodec接口提供压缩输出流和解压输入流
一旦启用了压缩机制,hadoop会为每条记录的key和value值进行压缩。IFile定义的文件格式非常简单,整个文件顺次保存数据记录,每条数据记录格式为:<key-len,value-len,key,value>

排序
排序是mapreduce框架中最重要的操作之一,map task和reduce task均会对数据进行排序,该操作属于hadoop默认行为,任何应用程序中的数据均会被排序,而不管逻辑上是否需要。
对于map task,它会将处理的结果暂时放在一个缓冲区中,当缓冲区使用率达到一定阀值后,再对缓冲区的数据进行一次排序,并将这些有序数据以IFile文件形式写到磁盘上,而当数据处理完毕后,它会对磁盘上所有的文件进行一次合并,以将这些文件合并成一个大的有序文件。
对于reduce task而言,它从每个map task上远程拷贝相应的数据文件,如果文件大小超过一定阀值,则放到磁盘上,否则放到内存中。如果磁盘上文件数目达到一定阀值,则进行一次合并以生成一个更大文件。如果内存中文件大小或者数目超过一定阀值,则进行一次合并后将数据写到磁盘中。当所有数据拷贝完毕后,reduce task统一对内存和磁盘上的所有数据进行一次合并。
快速排序:快速排序是应用最广泛的排序之一,它的基本思想是,选择序列中的一个元素作为枢轴,将小于枢轴的元素放在左边,将大于枢轴的元素放在右边,针对左右两个子序列重复此过程,直到序列为空或者只剩下一个元素。
枢轴选择:枢轴选择的好坏影响快速排序的性能,而最坏的情况是划分过程中使用产生两个极端不对称的子序列(一个长度为1,另一个长度为n-1),此时排序算法复杂度将增为O(N*N)。减少出现划分严重不对称的可能性,hadoop将序列的首尾和中间元素中的中位数作为枢轴。
子序列划分方法:hadoop使用了两个索引i和j分别从左右两端进行扫描序列,并让索引i扫描到大于等于枢轴的元素停止,索引j扫描到小于等于枢轴的元素停止,然后交换两个元素,重复这个过程直到两个索引相遇。
对相同元素的优化:在每次划分子序列时,将与轴枢轴相同的元素集中存放到中间位置,让它们不在参与后续递归处理,即将序列划分成三部分:小于枢轴、等于枢轴、大于枢轴。
减少递归次数:当子序列中元素数目小于13时,直接使用插入排序算法,不再继续递归。

优先队列:
文件归并有类merger完成,它要求待排序对象需要是segment实例化对象。segment是对磁盘和内存中的IFile格式文件的抽象。它具有类似于迭代器的功能,可迭代器读取IFile文件中的key/value。
merger采用了多轮递归合并的方式,每轮选取最小的前io.sort.factor(默认是10)个文件进行合并,并将产生的文件重新加入待合并列表中,知道剩下的文件数目小于io.sort.factor个,此时,它会返回指向由这些文件组成的小顶堆的迭代器。
merger采用了小顶堆实现,进而可将文件合并过程看作一个不断建堆的过程,
建堆->取堆顶元素->重新建堆->取堆顶元素…..
这里写图片描述

Reporter

所有task需周期性向tasktracker汇报最新进度和计数器值,而这正是有reporter组件实现的,在map/reduce task中,taskreporter类实现了Reporter接口,并且以现场形式启动,TaskReporter汇报的信息中包含两部分:任务执行进度和任务计数器值。
任务执行进度:
任务执行进度信息被封装到类Progress中,且每个progress实例以树的形式存在。hadoop采用了简单的线性模型计算每个阶段的进度值:如果一个大阶段可被分解成若干个子阶段,则可将大阶段看作一棵树的父节点,而子阶段可看作父节点对应的子节点,且大阶段的进度值可被均摊到各个子阶段中;如果一个阶段不可再分解,则该阶段进度值可表示成已读取数据量的比例。
对于map task而言,它作为一个大阶段不可再分解,为了简便,我们直接将已读取数据量占总数据量的比例作为任务当前执行进度值
对于 reducetask 而言,我们可将其分解成三个阶段:shuffle、sort和reduce,每个阶段占任务总进度的1/3,考虑到在shuffle阶段,reduce task需要从M(M 为map task 数目)个map task上读取一片数据,因此,可被分解成M个阶段,每个阶段占shuffle进度的1/M,可以看图所示
这里写图片描述

对于TaskReporter线程而言,它并不会总是每隔一段时间汇报进度和计数器值,而是仅当发现以下两种情况之一时才会汇报:
1、任务执行进度发生变化
2、任务的某个计数器值发生变化
在某个时间间隔内,如果任务执行进度和计数器值均未发生变化,则task只会简单地通过调用RPC函数ping探测taskTracker认为它处于悬挂状态,直接将其杀掉,为了防止某条记录因处理时间过长导致被杀,用户可采用以下两种方式:
1、每隔一段时间调用一次TaskReporter.progress()函数,以告诉Tasktracker自己仍然活着,
2、增大任务超时参数mapred.task.timeout(默认是10min)对应的值
任务计数器
任务计数器(counter)是hadoop提供的,用于实现跟踪任务运行进度的全局计数功能,用户可在自己的应用程序中添加计数器,任务计数器有2部分组成<name.value>,其中name表示计数器的名称,value表示计数器值(long类型)。计数器通常以组为单位管理,hadoop规定一个作业最多包含120个计数器(可通过参数mapreduce.job.counters.limit设定),50个计数器组。
对于同一个任务而言,所有任务包含的计数器相同,每个任务更新自己的计数器值,然后汇报给Tasktracker,并由Tasktracker通过心跳汇报给jobtracker,最后由jobtracker以作业为单位对所有计数器进行累加,作业的计数器分为两类,Mapreduce内置计数器和用户自定义计数器
1、mapreduce内置计数器
mapreduce框架内部为每个任务添加了三个计数器组,分别位于File input Format counters,File output Format Counters和map-reduce Framework中,他们包含的计数器为
这里写图片描述
这里写图片描述
这里写图片描述
用户自定义计数器
不同的编程接口,定义计数器的方式不同,其中主要介绍一下java的
hadoop为java应用程序提供了两种访问和使用计数器的方式:使用枚举类型和字符串类型,如果采用枚举类型,则计数器默认名称是枚举类型的java完全限定类名,这使得计数器名称的可读性很差,为此,hadoop提供了基于资源捆绑修改计数器显示名称的方法:以java枚举类型为名称创建一个属性文件,在该属性文件中,“counterGroupName”属性“字段类型.name”,属性值即为该计数器的显示名称。比如类task中定义了大量表示计数器的枚举类型,而这些计数器的显示名称被统一放到同目录下的属性文件task_counter.properties中,

counterGroupName=map-reduce frameworkMap_INPUT_RECORDS.name=map input records...

如果采用字符串类型,则用户可以直接在计数器API中指定计数器组,计数器名称和计数器值。基于枚举类型的字符串类型的计数器PAI如下

public abstract void incrCounter(Enum<?> key,long amount);public abstract void incrCounter(String group,String counter,long amout);
0 0