1. hadoop和spark的shuffle联系

两者都是将 mapper(Spark 里是 ShuffleMapTask)的输出进行 partition,不同的 partition 送到不同的 reducer(Spark 里 reducer 可能是DAG中下一个 stage 里的 ShuffleMapTask,也可能是 ResultTask)。

Reducer以内存作缓冲区,边 shuffle 边aggregate 数据,等到数据 aggregate 好以后进行 reduce() (Spark 里可能是后续的一系列操作)。Spark中的很多计算是作为MapReduce计算框架的一种优化实现。

2. hadoop和spark的shuffle区别

2.1 从底层来看

MapReduce 为了方便对存在于不同 partition的key/value Records进行Group,就提前对key进行排序,这样做的好处在于 combine/reduce() 可以处理大规模的数据。

Spark认为很多应用不需要对 key 排序,所以旧的Spark版本提供了基于hash的Shuffle写,通常使用 HashMap 来对shuffle 来的数据进行 aggregate,这种方法不会对数据进行提前排序。不过在1.2之后的版本默认方法还是基于排序的Shuffle,并且在在spark2.0及之后的版本中只存在SortShuffleManager而将原来的HashShuffleManager废弃掉

2.2 从实现角度来看 

Hadoop MapReduce 将处理流程划分出明显的几个阶段:map, spill, merge, shuffle, sort, reduce等。每个阶段各司其职,可以按照过程式的编程思想来逐一实现每个阶段的功能。

在 Spark 中,没有这样功能明确的阶段,只有不同的 stage 和一系列的 transformation操作,所以 spill, merge, aggregate 等操作需要蕴含在一些transformation操作中。

2.3 从数据流角度

Mapreduce只能从一个map stage接受数据。

Spark 可以从多个Map Stages来shuffle数据。这是 DAG 型数据流中宽依赖的优势,可以表达复杂的数据流操作。

2.4 从数据粒度角度

Spark 粒度更细,可以更即时的将获取到的 record 与 HashMap 中相同 key 的 records 进行合并。

2.5 从性能优化角度来讲

Spark考虑的更全面,针对不同类型的操作、不同类型的参数,会使用不同的 shuffle write 方式。

Shuffle write 有三种实现方式

  • BypassMergeSortShuffleHandle:map端没有聚合操作,且分区必须小于200

该handle对应的BypassMergeSortShuffleWriter,是开辟和后续RDD分区数量一样数量的小文件,读取每条记录算出它的分区号,然后根据分区号判断应该追加到该文件中,此外这个过程也有缓冲区的概念,但一般这个缓冲区都不会特别大,默认为32k。这也是这种shuffle写不支持map端聚合的一个原因,因为聚合必然要在内存中储存一批数据,将相同key的数据做聚合,而这里是直接开辟多个I/O流,根据分区号往文件中追加数据。

由于要同时打开多个文件,所以后续RDD的分区数也不能太多,否则同时打开多个文件,产生多个IO,消耗的资源成本很高。

  • SerializedShuffleHandle:map端没有聚合操作,需要Serializer支持relocation,分区数目必须小于16777216

该handle对应的是UnsafeShuffleWriter,序列化方式需要支持重定位,即使用KryoSerializer等一些序列化方式,这种方式下用到了Tungsten优化,排序的是二进制的数据,不会对数据进行反序列化操作,所以不支持aggregation。

分区数目必须小于16777216,原因是partition number是使用24bit 表示的:pow(2,24)=16777216。

  • SortShuffleWriter-BaseShuffleHandle:当以上情况都不满足时,采用这种ShuffleHandle

对应的ShuffleWrite是SortShuffleWriter,这种形式的支持map端聚合操作,而且也支持排序操作。