Shuffle作为处理连接map端和reduce端的枢纽,其shuffle的性能高低直接影响了整个程序的性能和吞吐量。
Shuffle本质上都是将Map端获得的数据使用分区器进行划分,并将数据发送给对应的 Reducer 的过程,map端的shuffle一般为shuffle的Write阶段,reduce端的shuffle一般为shuffle的read阶段。Hadoop和spark的shuffle在实现上面存在很大的不同。
1. Hadoop Shuffle
1.1 map端Shuffle
- input:根据split输入数据,运行map任务;
- patition:每个map task都有一个内存缓冲区,存储着map的输出结果;
- spill:当缓冲区快满的时候需要将缓冲区的数据以临时文件的方式存放到磁盘;
- merge:当整个map task结束后再对磁盘中这个map task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待reduce task来拉数据。
1.2 reduce 端Shuffle
reduce task在执行之前的工作就是不断地拉取当前job里每个map task的最终结果,然后对从不同地方拉取过来的数据不断地做merge,也最终形成一个文件作为reduce task的输入文件。
- copy:拉取数据。
- merge:合并拉取来的小文件
- reducer:计算
- output:输出计算结果
2. Spark Shuffle
在Spark的中,负责shuffle过程的执行、计算和处理的组件主要就是ShuffleManager,也即shuffle管理器。ShuffleManager随着Spark的发展有两种实现的方式,分别为HashShuffleManager和SortShuffleManager,因此spark的Shuffle有Hash Shuffle和Sort Shuffle两种。
2.1 HashShuffle
- 普通机制:因为其会产生M*R个数的巨量磁盘小文件而产生大量性能低下的Io操作,从而性能较低,因为其巨量的磁盘小文件还可能导致OOM
- 合并机制:通过重复利用buffer从而将磁盘小文件的数量降低到Core*R个,但是当Reducer 端的并行任务或者是数据分片过多的时候,依然会产生大量的磁盘小文件
2.2 SortShuffle
- 普通机制:在内存数据结构(默认为5M)完成排序,会产生2M个磁盘小文件
- 写入内存数据结构
- 根据不同的shuffle算子,可能选用不同的数据结构
- 定时器会检查内存数据结构的大小,如果内存数据结构空间不够,那么会申请额外的内存
- 排序:在溢写到磁盘文件之前,会先根据key对内存数据结构中已有的数据进行排序
- 溢写:排序过后,会分批将数据写入磁盘文件,默认的batch数量是10000条
- 合并:一个task将所有数据写入内存数据结构的过程中,会发生多次磁盘溢写操作,也就会产生多个临时文件,最后会将之前所有的临时磁盘文件都进行合并
- 写入内存数据结构
- bypass机制:SortShuffle的bypass机制不会进行排序,极大的提高了其性能
- 当shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值,会触发SortShuffle的bypass机制
- 算子不是聚合类的shuffle算子(比如reduceByKey)的时候会触发SortShuffle的bypass机制
3. Hadoop和spark的shuffle比较
3.1 从逻辑角度看
两者没有本质区别。Shuffle 过程就是一个 GroupByKey 的过程,只是 MapReduce 为了方便 GroupBy 存在于不同 partition 中的 key/value records,就提前对 key 进行排序。Spark 认为很多应用不需要,早期版本都没有对key进行排序,但新的版本其实也有对key进行排序。
3.2 从数据流角度看
两者有差别。MapReduce只能从一个 Map Stage shuffle 数据,Spark 可以从多个 Map Stages shuffle 数据(这是 DAG 型数据流的优势,可以表达复杂的数据流操作。
3.3 从Shuffle write/read实现看
有一些区别。以前对 shuffle write/read 的分类是 sort-based 和 hash-based。MapReduce 可以说是 sort-based,shuffle write 和 shuffle read 过程都是基于key sorting 的 (buffering records + in-memory sort + on-disk external sorting)。早期的 Spark 是 hash-based,shuffle write 和 shuffle read 都使用 HashMap-like 的数据结构进行 aggregate (without key sorting)。但目前的 Spark 是两者的结合体,shuffle write 可以是 sort-based (only sort partition id, without key sorting),shuffle read 阶段可以是 hash-based。因此,目前 sort-based 和 hash-based 已经“你中有我,我中有你”,界限已经不那么清晰。
3.4 从数据 fetch 与数据计算的重叠粒度看
两者有细微区别。MapReduce 是粗粒度,reducer fetch 到的 records 先被放到 shuffle buffer 中休息,当 shuffle buffer 快满时,才对它们进行 combine()。而 Spark 是细粒度,可以即时将 fetch 到的 record 与 HashMap 中相同 key 的 record 进行 aggregate。
3.5 从性能优化角度看
Spark考虑的更全面,MapReduce 的 shuffle 方式单一。Spark 针对不同类型的操作、不同类型的参数,会使用不同的shuffle write方式
参考