MapReduce流程

MapReduce流程

1.MapReduce架构

  Hadoop MapReduce采用了Master/Slave(M/S)架构,主要由以下几个组件组成:Client、JobTracker、TaskTracker和Task。如下图所示:
在这里插入图片描述
(1)Client

  用户编写的MapReduce程序通过Client提交到JobTracker端。在Hadoop内部用作业(Job)表示MapReduce程序,一个MapReduce可对应多个Job,每个Job会被分解成若干个Map/Reduce任务。

(2)JobTracker

  JobTracker主要负责资源监控和作业调度,其监控所有TaskTracker与Job的健康情况,一旦发现失败情况后,会将相应的任务转移到其他节点。同时,JobTracker还会跟踪任务的执行进度、资源使用量等信息,并将这些信息告诉任务调度器,调度器在资源出现空闲时,选择合适的任务使用这些资源。

(3)TaskTracker

  周期性地通过Heartbeat将本节点上资源的使用情况和任务的运行进度汇报给JobTracker,同时接收JobTracker发送过来的命令并执行相应的操作。TaskTracker使用“slot”等量划分本节点上的资源量。“slot”代表计算资源。一个Task获取到一个“slot”后才有机会运行,而Hadoop调度器的作用就是将各个TaskTracker上的空闲slot分配给Task使用。slot分为Map slot和Reduce slot两种,分别供Map Task和Reduce Task使用。

(4)Task

  Task分为Map Task和Reduce Task两种,均由TaskTracker启动。对于MapReduce来说,处理单位是split。split是一个逻辑概念,只包含一些元数据信息,比如数据起始位置、数据长度、数据所在节点等。其划分方式由用户自己决定。split的多少决定了Map Task的数目,每个split交由一个Map Task处理。
  Map Task执行过程如下图所示。其先将对应的split迭代解析成一个个key/value对,依次调用用户自定义的map()函数进行处理,最终将临时结果存放到本地磁盘上。其中临时数据被分成若干个partition,每个partition将被一个Reduce Task处理。
在这里插入图片描述
  Reduce Task执行过程如下图所示。该过程分三个阶段:1.从远程节点上读取Map Task中间结果(称为“Shuffle阶段”)。2.按照key对key/value对进行排序(称为“sort”阶段)。3.依次读取<key, value list>,调用用户自定义的reduce( )函数处理,并将最终结果存到HDFS上(称为“Reduce”阶段)。
在这里插入图片描述

2.简述MapReduce工作流程

  总得来说,MapReduce工作流程分为三个阶段:Map阶段、Shuffle阶段、Reduce阶段。

1.Map阶段

(1)分片

  Hadoop将MapReduce的输入数据划分成等长的小数据块,称为输入分片,简称分片。Hadoop为每个分片创建一个map任务,并由该任务来运行用户自定义的map函数,从而处理分片中的每条记录。最佳分片的大小应该与HDFS的块大小一样。

(2)解析

  map任务将输入的分片信息解析为key/value形式。

(3)map()函数处理

 将解析后的key/value数据由用户编写的map()函数进行处理,得到新的key/value中间结果,该中间结果还需要后续的Reduce阶段处理才会变成最终结果。

2.Shuffle阶段

map端

(4)写入缓冲区

 将map阶段处理得到的中间结果写入环形内存缓冲区,当缓冲区使用率超出阈值时要将内容溢写到磁盘中。

(5)排序

 在数据写入本地磁盘之前,线程会先根据最终要传的reducer把数据划分成相应的分区,在每个分区中,后台线程按键对其进行内存中排序(快排)。

(6)写入本地磁盘

 当缓冲区满了之后,将数据写到本地磁盘上,生成一个临时文件。

(7)合并

 当所有数据都处理完毕后,以分区为单位,对文件进行合并。最终使得每个Map Task只生成一个大文件。

reduce端

(8)复制

 从各个Map Task上远程复制数据,大数据写到磁盘上,小数据放入内存。由于每个Map Task的完成时间可能不同,因此在每个Task完成时,reduce就开始拷贝其输出。

(9)合并

 拷贝数据的同时,对文件进行合并。

(10)排序

 复制完所有map输出之后,对所有数据进行一次归并排序(归并+堆排)。

3.Reduce阶段

(11)reduce()函数处理

 将每组数据依次交给用户编写的reduce()函数处理。

(12)写入HDFS

 将最终结果写入HDFS。

3.深入MapReduce工作流程

 下面以Task的角度来看下具体在MapReduce工作流程中每一步是怎么去做的。

1.Map Task

  Map过程主要涉及到四种task,分别是Job-setup Task、Job-cleanup Task、Task-cleanup Task和Map Task。
 其中,Job-setup Task和Job-cleanup Task分别是作业运行时启动的第一个任务和最后一个任务,主要工作分别是进行一些作业初始化和收尾工作,比如创建和删除作业临时输出目录。
 Task-cleanup Task是任务失败或者被杀死后,用于清理已写入临时目录中数据的任务。
 至于Map Task,显然是map阶段最重要的任务。

Map Task工作流程

(1)Read阶段
  Map Task通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。

(2)Map阶段
 将解析出的key/value交给用户编写的map()函数处理,并产生一系列新的key/value。

(3)Collect阶段
  在用户编写的map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会通过调用Partitioner将生成的key/value分配,并写入一个环形内存缓冲区中。

(4)Spill阶段
  即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。

(5)Combine阶段
 当所有数据处理完成后,Map Task对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。

在这里插入图片描述

Collect过程

  在Collect阶段,等到map()函数处理完一对key/value,并产生新的key/value后,会调用OutputCollector.collect()函数输出结果。
  用户在map()函数中调用OldOutputCollector.collect(key,value)后,在该函数内部,首先会调用Partitioner.getPartition()函数获取记录的分区号partition,然后将三元组<key, value, partition>传递给MapOutputBuffer.collect()函数做进一步处理。
  MapOutputBuffer内部使用了一个缓冲区暂时存储用户输出数据,当缓冲区使用率达到一定阈值后,就开始将缓冲区中的数据写入磁盘,与此同时,生产者还是继续保持向缓冲区写入数据,进而达到真正的读写并行。
  MapOutputBuffer内部采用了两级索引结构,涉及三个环形内存缓冲区,分别是kvoffsets、kvindices和kvbuffer。
在这里插入图片描述
  下面看看这三个缓冲区的写入过程

1.kvoffsets

  偏移量索引数组,用于保存key/value信息在位置索引kvindices中的偏移量。由于一对key/value占用数组kvoffsets的1个int大小,数组kvindices的3个int大小,所以kvoffsets与kvindices的内存空间分配之比为1:3。
  kvoffsets的写入过程由指针kvstart、kvend、kvindex控制,其中kvstart表示存有数据的内存段初始位置,kvindex表示未存储数据的内存段初始位置。
(1)写入缓冲区
 写入缓冲区时,有下面两种情况:
在这里插入图片描述
在这里插入图片描述

(2)溢写到磁盘
  当kvoffsets内存空间使用率超过80%后,需要将内存中数据写到磁盘上。若kvindex>kvend,则kvoffsets已使用内存为kvindex-kvend;否则,已使用内存为kvoffsets.length-(kvend-kvindex)。

2.kvindices

  位置索引数组,用于保存key/value值在数据缓冲区kvbuffer中的起始位置。

3.kvbuffer

  数据缓冲区,用于保存实际的key/value值。
  kvbuffer的读写操作由指针bufstart、bufend、bufindex、bufmark、bufvoid控制。bufvoid指向kvbuffer中有效内存结束为止,kvbuffer表示最后写入的一个完整key/value块结束位置。
  写入情况如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Spill过程

  Spill过程由SpillThread线程完成,而SpillThread线程实际上是缓冲区kvbuffer的消费者,其主要代码如下:

spillLock.lock();
while(true){
	spillDone.signal();
	while(kvstart == kvend) {
		spillReady.await();
	}
	spillLock.unlock();
	sortAndSpill();		//排序,然后将缓冲区kvbuffer中的数据写到磁盘上
	spillLock.lock();	//重置各个指针,以便为下一次溢写做准备
	if(bufend < bufindex && bufindex < bufstart) {
		bufvoid = kvbuffer.length();
	}
	vstart = kvend;
	bufstart = bufend;
}
spillLock.unlock();

  线程SpillThread调用函数sortAndSpill()将环形缓冲区kvbuffer中区间[bufstart,bufend]内的数据写到磁盘上。函数sortAndSpill()内部工作流程如下:
1.利用快排对缓冲区中的数据进行排序,先按照分区编号partition排序,然后按照key进行排序。经过排序后,数据以分区为单位聚集在一起,且每一分区中所有数据按照key有序。
2.按照分区编号由小到大一次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示当前溢出次数)中。如果用户设置了Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。
3.将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。

Combine过程

  当所有数据处理完后,Map Task会将所有临时文件合并成一个大文件,并保存到文件output/file.out中,同时生成相应的索引文件output/file.out.index。
  在进行文件合并过程中,Map Task以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式:每轮合并100个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。

2.Reduce Task

  Reduce过程也主要涉及到四种task,分别是Job-setup Task,Job-cleanup Task,Task-cleanup Task和Reduce Task。
  前三种Task和Map阶段的是一样的,而Reduce Task要从各个Map Task上读取一片数据,经排序后,以组为单位交给用户编写的reduce()函数处理,并将结果写到HDFS上。

Reduce Task工作流程

(1)Shuffle阶段
  又称为copy阶段,Reduce Task从各个Map Task上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,将其写到磁盘上,否则直接放到内存中。

(2)Merge阶段
  在远程拷贝数据的同时,Reduce Task启动了两个后台线程对内存和磁盘上的文件进行合并,以防内存使用过多或磁盘上文件过多。

(3)Sort阶段
  由于各个Map Task已经实现对自己的处理结果进行了局部排序,因此,Reduce Task只需要对所有数据进行一次归并排序即可。

(4)Reduce阶段
  Reduce Task将每组数据依次交给用户编写的reduce()函数处理

(5)Write阶段
  reduce()函数将计算结果写到HDFS中。
在这里插入图片描述

Shuffle和Merge过程

  在Reduce Task中,Shuffle阶段和Merge阶段是并行进行的。当远程拷贝数据量达到一定阈值后,便会触发相应的合并线程对数据进行合并。
  总体来看,Shuffle和Merge阶段可进一步划分为三个子阶段。

1.准备运行完成的Map Task列表
  GetMapEventsThread线程周期性通过RPC从TaskTracker获取已完成Map Task列表,并保存到映射表mapLocations中。为防止出现数据访问热点(大量数据集中读取某个TaskTracker上的数据),Reduce Task通过所有TaskTracker Host进行“混洗”操作以打乱数据拷贝顺序,并将调整后的Map Task输出数据位置保存到scheduledCopies列表中。

2.远程拷贝数据
  Reduce Task同时启动多个MapOutputCopier线程,这些线程从scheduledCopies列表中获取Map Task输出位置,并通过HTTP Get远程拷贝数据。对于获取的数据分片,如果大小超过一定阈值,则存放到磁盘上,否则直接放到内存中。

3.合并内存文件和磁盘文件
  为了防止内存或者磁盘上的文件数据过多,Reduce Task启动了LocalFSMerge和InMemFSMergeThread两个线程分别对内存和磁盘上的文件进行合并。
  对于磁盘上文件,当文件数目超过2*ioSortFactor-1后,线程LocalFSMerge线程会从列表mapOutputFilesOnDisk中取出最小的ioSortFactor个文件进行合并,并将合并后的文件再次写到磁盘上。
  对于内存中的文件,当满足下面几个条件之一时,InMemFSMergeThread线程会将内存中所有数据合并后写到磁盘上:
  (1)所有数据拷贝完毕后,关闭ShuffleRamManager。
  (2)ShuffleRamManager中已使用内存超过可用内存的60%且内存文件数目超过2个。
  (3)内存中的文件数目超过1000。
  (4)阻塞在ShuffleRramManager上的请求数目超过拷贝线程数目的75%。

Sort和Reduce过程

 根据MapReduce语义,Reduce Task需要将key值相同的数据聚集到一起,并按组将数据交给reduce()函数处理。为此,Hadoop采用了基于排序的数据聚集策略。
 由于之前Map Task已经事先对自己的输出分配进行了局部排序,所以Reduce Task只需要进行一次归并排序就可以保证数据整体有序。Hadoop将Sort阶段和Reduce阶段并行化。
 在Sort阶段,Reduce Task为内层和磁盘中的文件建立了小顶堆,保存了指向该小顶堆根节点的迭代器。
 在Reduce阶段,Reduce Task不断移动迭代器,以将key相同的数据顺次交给reduce()函数处理,期间移动迭代器的过程实际上就是不断调整小顶堆的过程,这样,Sort和Reduce就可以并行进行。

4.Shuffle阶段涉及的两次排序

1.map端的快速排序

 上面提到过,在Map阶段,要把环形缓冲区中的数据写入磁盘之前要进行一次排序,这里使用的是快速排序。Hadoop中使用的快速排序算法较我们平时使用的快排算法做了些优化。

(1)枢轴选择

 平时使用的快排算法通常选择序列的最后一个元素或者第一个元素作为枢轴
 Hadoop则是使用序列的中位数。这样减少了划分严重不对称的可能性。

(2)子序列划分方法

 平时使用的快排算法通常是使用一个索引i由前往后扫描
 Hadoop则是让两个索引i和j分别从左右两端进行扫描,当索引i扫描到大于等于中轴的元素停止,索引j扫描到小于等于枢轴的元素停止,然后交换两个元素,重复该过程直到两索引相遇。

(3)对相同元素的划分

 平时使用的快排算法只是将序列划分为小于枢轴和大于枢轴两个区间。
 Hadoop在每次划分子序列时,将与枢轴相同的元素集中存放到中间位置,让它们不再参与后续递归处理,将序列划分成三个区间:小于枢轴、等于枢轴、大于枢轴。

(4)减少递归次数

 平时使用的快排算法直到序列为空或只剩一个元素时停止。
 Hadoop使用的快排当子序列中元素数目小于13时,直接使用插入排序算法,不再继续递归。

 其快排大致如下图所示:
在这里插入图片描述

2.reduce端的归并排序

 上面提到过,在Reduce阶段中有一个Sort阶段,此时要将之前在Map阶段已经进行过快速排序的文件进行归并排序。
 这里的归并排序其实就是一个归并+堆排序的过程,在归并的同时进行堆排序。

(1)归并
 文件归并由类Merge完成,它要求待排序对象是Segment实例化对象。Segment是对磁盘合内存中的IFile格式文件的抽象,它具有类似于迭代器的功能,可迭代读取IFile文件中的key/value。
 Merge采用了多轮递归合并的方式,每轮选取最小的前10个(可调参数)文件进行合并,并将产生的文件重新加入待合并列表中,直到剩下的文件数小于10个,此时,它会返回指向由这些文件组成的小顶堆的迭代器。
 下面展现一下io.sort.factor为3,也就是每次选取最小的前3个文件进行合并的过程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)堆排序
 上述合并过程其实和哈夫曼树的合并过程是差不多的。在对文件进行合并的同时,也在进行着堆排序。在每一轮合并过程中,Merger采用了小顶堆实现,进而可将文件合并过程看作一个不断建堆的过程:建堆→取堆顶元素→重新建堆→取堆顶元素…
 下面看看在上面归并文件的过程中,堆排序过程中小顶堆是如何变化的:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

loser与你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值