MapReduce——Shuffle过程

时光毁灭记忆、已成空白 提交于 2020-04-05 19:04:48

Shuffle的本意是洗牌、混洗,把一组有一定规则的数据尽量转换成一组无规则的数据,越随机越好。MapReduce中的Shuffle更像是洗牌的逆过程,把一组无规则的数据尽量转化成一组具有一定规则的数据。

为什么MapReduce计算模型需要Shuffle过程?

 我们都知道MapReduce计算模型一般包括两个重要的阶段:Map是映射,负责数据的过滤分发;Reduce是规约,负责数据的计算归并。Reduce数据来源于Map,Map的输出即是Reduce的输入,Reduce需要通过Shuffle来获取数据。从Map输出到Reduce输入的整个过程可以广义地称为Shuffle(Reduce开始之前吧对应的数据从每个map输出的中间结果拷贝过来,这个过程称为copy,拷贝中间结果过来后,会经过合并、排序等操作,会产生一个排序的输入文件,这个过程为sort,copy和sort两个过程合起来也称为Shuffle过程。)Shuffle横跨Map端和Reduce端,在Map端包括Spill过程,在reduce端包括copy和sort过程。

MapReduce详细流程

1、切片

  • 在FileInputFormat中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize))
  • minSize的默认值是1,而maxSize的默认值是long类型的最大值,即可得切片的默认大小是blockSize(128M)
  • maxSize参数如果调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值
  • minSize参数调的比blockSize大,则可以让切片变得比blocksize还大
  • hadoop为每个分片构建一个map任务,可以并行处理多个分片上的数据,整个数据的处理过程将得到很好的负载均衡,因为一台性能较强的计算机能处理更多的数据分片.
  • 分片也不能切得太小,否则多个map和reduce间数据的传输时间,管理分片,构建多个map任务的时间将决定整个作业的执行时间.(大部分时间都不在计算上)

如果文件大小小于128M,则该文件不会被切片,不管文件多小都会是一个单独的切片,交给一个maptask处理.如果有大量的小文件,将导致产生大量的maptask,大大降低集群性能.

大量小文件的优化策略:

  1.  在数据处理的前端就将小文件整合成大文件,再上传到hdfs上,即避免了hdfs不适合存储小文件的缺点,又避免了后期使用mapreduce处理大量小文件的问题。(最提倡的做法)
  2. 小文件已经存在hdfs上了,可以使用另一种inputformat来做切片(CombineFileInputFormat),它的切片逻辑和FileInputFormat(默认)不同,它可以将多个小文件在逻辑上规划到一个切片上,交给一个maptask处理。

2、环形缓冲区

  • 经过map函数的逻辑处理后的数据输出之后,会通过OutPutCollector收集器将数据收集到环形缓冲区中保存
  • hadoop2.0中,环形缓冲区默认大小为100M,当保存数据达到80%,就会将缓冲区的数据溢写到磁盘上保存

3、Spill过程(溢写)

spill过程包括输出、排序、溢写、合并等步骤,如图所示:

  • 环形缓冲区的数值达到80%时就会溢写到磁盘上进行保存,在此过程中,程序会对数据进行分区(默认HashPartition)和排序(默认根据key进行快排)
  • 缓存区不断溢写出的数据形成多个小文件

4、Meage(合并)

  • 溢写出的多个小文件各个区合并在一起,(0区和0区合并为一个0区),形成大文件
  • 通过归并排序保证区内的数据有序

5、Shuffle

从过程2到过程7之间,即map任务和reduce任务之间的数据流称为shuffle(混洗),而过程5最能体现出混洗这一概念。一般情况下,一个reduce任务的输入数据来自与多个map任务,多个reduce任务的情况下就会出现如过程5所示的,每个reduce任务从map的输出数据中获取属于自己的那个分区的数据。

6、合并

运行reducetask的节点通过过程5,将来自多个map任务的属于自己的分区数据下载到本地磁盘工作目录。这多个分区文件通过归并排序合并成大文件,并根据key值分好组(key值相同的,value值会以迭代器的形式组在一起)。

7、reducetask

reducetask从本地工作目录获取已经分好组并且排好序的数据,将数据进行reduce函数中的逻辑处理。

8、输出

每个reducetask输出一个结果文件。

Partition(分区)

数据从环形缓存区溢出到文件的过程中会根据用户自定义的partition函数进行分区,如果用户没有自定义该函数,程序会用默认的partitioner通过哈希函数来分区,hash partition 的好处是比较弹性,跟数据类型无关,实现简单,只需要设置reducetask的个数。分区的目的是将整个大数据块分成多个数据块,通过多个reducetask处理后,输出多个文件。通常在输出数据需要有所区分的情况下使用自定义分区,如在上述的流量统计的案例里,如果需要最后的输出数据再根据手机号码的省份分成几个文件来存储,则需要自定义partition函数,并在驱动程序里设置reduce任务数等于分区数(job.setNumReduceTasks(5);)和指明自己定义的partition(job.setPartitionerClass(ProvincePartitioner.class))。在需要获取统一的输出结果的情况下,不需要自定义partition也不用设置reducetask的数量(默认1个)。

自定义的分区函数有时会导致数据倾斜的问题,即有的分区数据量极大,各个分区数据量不均匀,这会导致整个作业时间取决于处理时间最长的那个reduce,应尽量避免这种情况发生。

分组

分组和上面提到的partition(分区)不同,分组发生在reduce端,reduce的输入数据,会根据key是否相等而分为一组,如果key相等的,则这些key所对应的value值会作为一个迭代器对象传给reduce函数。以单词统计为例,reduce输入的数据就如:第一组:(a,(1,3,5,3,1))第二组:(b,(6,2,3,1,5))。上述例子也可以看出在map端是执行过combiner函数的,否则reduce获得的输入数据是:第一组:(a,(1,1,1,1,1,...))第二组:(b,(1,1,1,1,1...))。对每一组数据调用一次reduce函数。

值得一提的是如果key是用户自定义的bean对象,那么就算两个对象的内容都相同,这两个bean对象也不相等,也会被分为两组。如上述流量统计案例里自定义的flowbean对象,就算是上行流量下行流量相等的两个flowbean对象也不会被分为一组。这种bean作为key的情况下,如果处理逻辑需要将两个bean归为一个组,则需要另外的方法(我会在之后的文章中给出)。

sort(排序)

在整个mapreduce过程中涉及到多处对数据的排序,环形缓存区溢出的文件,溢出的小文件合并成大文件,reduce端多个分区数据合并成一个大的分区数据等都需要排序,而这排序规则是根据key的compareTo方法来的。

map端输出的数据的顺序不一定是reduce端输入数据的顺序,因为在这两者之间数据经过了排序,但reduce端输出到文件上显示的顺序就是reduce函数的写出顺序。在没有reduce函数的情况下,显示地在驱动函数里将reduce的数量设置为0(设置为0后表示没有reduce阶段,也就没有shuffle阶段,也就不会对数据进行各种排序分组),否则虽然没有reduce逻辑,但是还是会有shuffle阶段,map端处理完数据后将数据保存在文件上的顺序也不是map函数的写出顺序,而是经过shuffle分组排序过后的顺序

combiner(map端的reduce)

集群的带宽限制了mapreduce作业的数量,因此应该尽量避免map和reduce任务之间的数据传输。hadoop允许用户对map的输出数据进行处理,用户可自定义combiner函数(如同map函数和reduce函数一般),其逻辑一般和reduce函数一样,combiner的输入是map的输出,combiner的输出作为reduce的输入,很多情况下可以直接将reduce函数作为conbiner函数来使用(job.setCombinerClass(FlowCountReducer.class);)。combiner属于优化方案,所以无法确定combiner函数会调用多少次,可以在环形缓存区溢出文件时调用combiner函数,也可以在溢出的小文件合并成大文件时调用combiner。但要保证不管调用几次combiner函数都不会影响最终的结果,所以不是所有处理逻辑都可以使用combiner组件,有些逻辑如果在使用了combiner函数后会改变最后rerduce的输出结果(如求几个数的平均值,就不能先用combiner求一次各个map输出结果的平均值,再求这些平均值的平均值,这将导致结果错误)。

combiner的意义就是对每一个maptask的输出进行局部汇总,以减小网络传输量。(原先传给reduce的数据是(a,(1,1,1,1,1,1...)),使用combiner后传给reduce的数据变为(a,(4,2,3,5...)))

MapTask和ReduceTask

有几个maptask是由程序决定的,默认情况下使用FileInputFormat读入数据,maptask数量的依据有一下几点:

  1. 文件大小小于128M(默认)的情况下,有几个文件就有几个maptask
  2. 大于128M的文件,根据切片规则,有几个分片就有几个maptask
  3. 并不是maptask数量越多越好,太多maptask可能会占用大量数据传输等时间,降低集群计算时间,降低性能。大文件可适当增加blocksize的大小,如将128M的块大小改为256M或512M,这样切片的大小也会增大,切片数量也就减少了,相应地减少maptask的数量。如果小文件太多,可用上述提到过的小文件优化策略减少maptask的数量。

有几个reducetask是用户决定的,用户可以根据需求,自定义相应的partition函数,将数据分成几个区,相应地将reducetask的数量设置成分区数量。(设置5个reducetask,job.setNumReduceTasks(5))

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!