大话Spark(4)-一文理解MapReduce Shuffle和Spark Shuffle

Shuffle本意是 混洗, 洗牌的意思, 在MapReduce过程中需要各节点上同一类数据汇集到某一节点进行计算,把这些分布在不同节点的数据按照一定的规则聚集到一起的过程成为Shuffle.

在Hadoop的MapReduce框架中, Shuffle是连接Map和Reduce之间的桥梁, Map的数据要用到Reduce中必须经过Shuffle这个环节. 由于Shuffle涉及到磁盘的读写和网络的传输, 所以Shuffle的性能高低直接影响到整个程序的性能和吞吐量.

MapReduce中的Shuffle

这张图是官网对Shuffle过程的描述,我们来分别看下map端和reduce端做了什么, 如何做的.

Map端

  1. map执行task时, 输入数据来源于HDFS的block, 在MapReduce概念中, map的task只读取split. split与block的对应关系可能是多对一, 默认是一对一. 
  2. map在写磁盘之前, 会根据最终要传给的reduce把数据划分成相应的分区(partition). 每个分区中,后台线程按键进行排序,如果有combiner,它在排序后的输出上运行.(combiner可以使map的结果更紧凑,减少写磁盘的数据和传递给reduce的数据[省空间和io])
  3. map产生文件时, 并不是简单地将它写到磁盘. 它利用缓冲的方式把数据写到内存并处于效率的考虑进行与排序.(如图中 buffer in memory). 每一个map都有一个环形内存缓冲区用于存储任务输出.缓冲区大小默认100MB, 一旦达到阈值(默认80%), 一个后台线程便开始把内容溢出(split)到磁盘.(如果在此期间[split期间]缓冲区被填满,map会被阻塞,直到写磁盘过程完成. 
  4. 每次内存缓冲区达到阈值移出,就会新建一个溢出文件(split file)(上图 partition,sort and split to disk). 因此在map任务最后一个记录输出之后,任务完成之前会把一出的多个文件合并成一个已分区且已排序的输出文件.(上图 merge on task)

Reduce端

  1. map的输出文件在map运行的机器的本地磁盘(reduce一般不写本地), map的输出文件包括多个分区需要的数据, reduce的输入需要集群上多个map的输出. 每个map的完成时间可能不同, 因此只要有一个map任务完成, reduce就开始复制其输出.(上图 fetch阶段) reduce有少量复制线程(默认5个),因此能够并行取得map输出(带宽瓶颈).
  2. reduce如何知道从哪台机器获取map结果? map执行完会通知master, reduce总有一个线程定期轮询master(心跳)可以获得map输出的位置. master会等到所有reduce完成之后再通知map删除其输出.
  3. 如果map的输出很小,会被复制到reduce任务jvm的内存.否则map输出会被复制到磁盘(又写磁盘)
  4. 复制完所有map输出后,reduce任务进入排序合并阶段(其实是合并阶段,因为map的输出上有序的).这个维持顺序的合并过程是循环进行的.(比如50个map输出,合并因子是10(默认值), 则合并将进行5次, 每次合并10个文件, 最终有5个中间文件)
  5. 在最后reduce阶段,直接把数据输入reduce函数(上面的5个中间文件不会再合并成一个已排序的中间文件). 输出直接写到文件系统, 一般为HDFS. 

map输出为什么要排序?

  1. key存在combine操作,排序之后相同的key在一起方便合并.
  2. reduce按照key读数据时, 按照key的顺序去读, 遇到不一样的 key时即可知道之前的key的数据是否读取完毕. 如果没排序,则需要把全部数据都做处理. 

上面就是MapReduce的Shuffle过程, 其实Spark2.0之后的Shuffle过程与MapReduce的基本一致,都是基于排序的,早期spark版本中的shuffle是基于hash的,让我们来一起看下.

Spark中的Shuffle

Spark有两种Shuffle机制. 一种是基于Hash的Shuffle, 还有一种是基于Sort的Shuffle.在Shuffle机制转变的过程中, 主要的一个优化点就是产生的小文件个数.


以上图为例,在Spark的算子reduceByKey(_ + _, 2)产生的shuffle中,我们先看Shuffle Write阶段.

Shuffle Write (Hash-based)


如图所示, hash-based的Shuffle, 每个map会根据reduce的个数创建对应的bucket, 那么bucket的总数量是: M * R (map的个数 * reduce的个数).
(假如分别有1k个map和reduce,将产生1百万的小文件!)
如上图所示,2个core, 4个map task, 3个reduce task 产生了4*3 = 12个小文件.(每个文件中是不排序的)

Shuffle Write (Hash-based) 优化!

由于hash-based产生的小文件太多, 对文件系统的压力很大, 后来做了优化. 
把同一个core上的多个map输出到同一个文件. 这样文件数就变成了 core * R个.如下图:

2个core, 4个map task, 3个 reduce task, 产生了2*3 = 6个文件.
(每个文件中仍然不是排序的)

Shuffle Write (Sort-based)

由于优化后的hash-based Shuffle的文件数为: core * R, 产生的小文件仍然过大, 所以引入了 sort-based Shuffle


sort-based Shuffle中, 一个map task 输出一个文件.
文件在一些到磁盘之前, 会根据key进行排序. 排序后, 分批写入磁盘. task完成之后会将多次溢写的文件合并成一个文件. 由于一个task只对应一个磁盘文件, 因此还会单独写一份索引文件, 标识下游各个task的数据对应在文件中的起始和结束offset.

Shuffle Read


目前,hash-based 和 sort-based写方式公用相同的shuffle read. 
如下图所示: 

 


shuffle read task从多个map的输出文件中fetch自己需要的已排序好的数据. 
read task 会先从索引文件中获取自己需要的数据对应的索引, 在读文件的时候跳过对应的Block数据区, 只读当前自己这个task需要的数据. 

什么时候开始fetch数据?

当 parent stage 的所有ShuffleMapTasks结束后再fetch(这里和MapReduce不同). 理论上讲, 一个ShuffleMapTask结束后就可以fetch, 但是为了迎合 stage 的概念(即一个stage如果其parent stages没有执行完,自己是不能被提交执行的),还是选择全部ShuffleMapTasks执行完再去 etch.因为fetch来的 FileSegments要先在内存做缓冲(默认48MB缓冲界限), 所以一次fetch的 FileSegments总大小不能太大. 一个 softBuffer里面一般包含多个 FileSegment,但如果某个FileSegment特别大的话, 这一个就可以填满甚至超过 softBuffer 的界限.

边 fetch 边处理还是一次性 fetch 完再处理?

边 fetch 边处理.本质上,MapReduce shuffle阶段就是边fetch边使用 combine()进行处理,只是combine()处理的是部分数据. MapReduce为了让进入 reduce()的records有序, 必须等到全部数据都shuffle-sort后再开始 reduce(). 因为Spark不要求shuffle后的数据全局有序,因此没必要等到全部数据 shuffle完成后再处理. 
那么如何实现边shuffle边处理, 而且流入的records是无序的?答案是使用可以 aggregate 的数据结构, 比如 HashMap. 每从shuffle得到(从缓冲的 FileSegment中deserialize出来)一个 <key, value="">record, 直接将其放进 HashMap 里面.如果该HashMap已经存在相应的 Key. 那么直接进行 aggregate 也就是 func(hashMap.get(Key), Value).

Shuffle aggregate

shuffle read task拿到多个map产生的相同的key的数据后,需要对数据进行聚合,把相同key的数据放到一起,这个过程叫做aggregate.


大致过程如下图: 


task把读来的 records 被逐个 aggreagte 到 HashMap 中,等到所有 records 都进入 HashMap,就得到最后的处理结果。

fetch 来的数据存放到哪里?

刚 fetch 来的 FileSegment 存放在 softBuffer 缓冲区,经过处理后的数据放在内存 + 磁盘上。

小结:

其实MapReduce Suffle 和 Spark的Shuffle在主要方面还是基本一致的, 比如:都是基于sort的. 
细节上有一些区别, 比如 mapreduce完成一个map,就开始reduce, 而spark由于stage的概念,需要等所有ShuffleMap完成再ShuffleReduce.





猜你喜欢

转载自www.cnblogs.com/wangtcc/p/10936521.html