MapReduce的shuffle和Spark的shuffle的之间的关系

通过前面对两种shuffle的理解,我们总结如下:

  1. 从shuffle的原理来看,两者其实差别不大。都是将Mapper(spark里是shuffleMapTask)的输出按照reduce端的并行度进行分区,不同的partition送到不同的reducer(Spark里 reducer 可能是下一个 stage 里的 ShuffleMapTask,也可能是 ResultTask)。Reducer以内存作为缓冲区,便shuffle边aggregate数据,等到数据 aggregate 好以后进行 reduce() (Spark 里可能是后续的一系列操作)。

  2. 从其细节来看,两者差别又不小。

shuffle manager

  • Hadoop shuffle过程是基于sort-based过程,在shuffle过程中会发生排序行为。
  • spark1.1之前,只有hash-based。spark1.1时有 hash和sort两种方式,spark1.2之后将hash变为sort。spark2.0之后,hash不在使用。根据业务场景需要进行shuffle Manager的选择。--Hash Shuffle Manager / Sort ShuffleManager(普通模式和bypass模式)。

Shuffle过程排序次数

  • Hadoop Shuffle过程总共会发生3次排序行为,详细分别如下:
  1. 第一次排序行为:在map阶段,由环形缓冲区溢出到磁盘上时,落地磁盘的文件会按照key进行分区和排序,属于分区内有序,排序算法为快速排序;
  2. 第二次排序行为:在map阶段,对溢出的文件进行combiner合并过程中,需要对溢出的小文件进行归并排序、合并,排序算法为归并排序;
  3. 第三次排序行为:在reduce阶段,reducetask将不同maptask端文件拉去到同一个reduce分区后,对文件进行合并,归并排序,排序算法为归并排序;(这里有内存到内存,内存到磁盘,磁盘到磁盘)。
  • Spark Shuffle过程在满足Shuffle Manager为SortShuffleManager,且运行模式为普通模式的情况下才会发生排序行为,排序行为发生在数据结构中保存数据内存达到阈值,在溢出磁盘文件之前会对内存数据结构中数据进行排序;
  1. Spark中Sorted-Based Shuffle在Mapper端是进行排序的,包括partition的排序和每个partition内部元素进行排序。(其内部数据结构看细解spark的shuffle - 掘金 (juejin.cn))。但是在Reducer端没有进行排序,所以job的结果默认情况下不是排序的。

2.Sorted-Based Shuffle 采用Tim-Sort排序算法,好处是可以极为高效的使用Mapper端的排序成果完成全局排序。

Shuffle 逻辑流划分

  • Hadoop Shuffle过程可以划分为:map(),spill,merge,shuffle,sort,reduce()等,是按照流程顺次执行的,属于Push类型;
  • Spark Shuffle过程是由算子进行驱动,由于Spark的算子懒加载特性,属于Pull类型,整个Shuffle过程可以划分为Shuffle Write 和Shuffle Read两个阶段;

数据结构不同

  • Hadoop 是基于文件的数据结构;
  • Spark 是基于RDD的数据结构,计算性能要比Hadoop要高;

Shuffle Copy的方式

  • Hadoop MapReduce采用框架jetty的方式;
  • Spark HashShuffle采用netty或者是socket流

Shuffle Fetch后数据存放位置

  • Hadoop reduce端将map task的文件拉去到同一个reduce分区,是将文件进行归并排序、合并,将文件直接保存在磁盘上;在 Hadoop MapReduce 中,默认将 reducer 的 70% 的内存空间用于存放 shuffle 来的数据,等到这个空间利用率达到 66% 的时候就开始 merge-combine()-spill。
  • Spark Shuffle Read 拉取来的数据首先肯定是放在Reducer端的内存缓存区中的(Spark曾经有版本要求只放在内存缓存中,数据结构类似于HashMap(AppendOnlyMap)显然特别消耗内存和极易出现OOM,同时也从Reducer端极大的限制了Spark集群的规模),现在的实现都是内存+磁盘的方式(数据结构使用ExternalAppendOnlyMap),当然也可以通过Spark.shuffle.spill=false来设置只能使用内存。使用ExternalAppendOnlyMap的方式时候如果内存使用达到一定临界值,会首先尝试在内存中扩大ExternalAppendOnlyMap(内部有实现算法),如果不能扩容的话才会spill到磁盘。在 Spark 中,一旦 ExternalAppendOnlyMap 达到一个阈值就开始 spill。

Fetch操作与数据计算粒度

  • Hadoop的MapReduce是粗粒度的,Hadoop Shuffle Reducer Fetch到的数据record先暂时被存放到Buffer 中,当Buffer快满时才进行combine()操作,只是 combine() 处理的是部分数据。MapReduce 为了让进入 reduce() 的 records 有序,必须等到全部数据都 shuffle-sort 后再开始 reduce()。
  • Spark的Shuffle Fetch是细粒度的,Reducer是对Map端数据Record边拉去边聚合;因为 Spark 不要求 shuffle 后的数据全局有序,因此没必要等到全部数据 shuffle 完成后再处理。

例子:对于wordcount例子来说,

  • mapreduce的reduce端,对于拉取的map数据,如果设置了conbiner,那么会在spill磁盘前继续聚合操作。然后归并文件,进行redcue方法。
  • spark是是使用可以 aggregate 的数据结构,比如 HashMap。每 shuffle 得到(从缓冲的 FileSegment 中 deserialize 出来)一个 \record,直接将其放进 HashMap 里面。如果该 HashMap 已经存在相应的 Key,那么直接进行 aggregate 也就是 func(hashMap.get(Key), Value),比如上面 WordCount 例子中的 func 就是 hashMap.get(Key) + Value,并将 func 的结果重新 put(key) 到 HashMap 中去。这个 func 功能上相当于 reduce()。但实际处理数据的方式与 MapReduce reduce() 有差别,差别相当于下面两段程序的差别。
// MapReduce
  reduce(K key, Iterable<V> values) { 
      result = process(key, values)
      return result    
  }

  // Spark
  reduce(K key, Iterable<V> values) {
      result = null 
      for (V value : values) 
          result  = func(result, value)
      return result
  }

复制代码

MapReduce 可以在 process 函数里面可以定义任何数据结构,也可以将部分或全部的 values 都 cache 后再进行处理,非常灵活。而 Spark 中的 func 的输入参数是固定的,一个是上一个 record 的处理结果,另一个是当前读入的 record,它们经过 func 处理后的结果被下一个 record 处理时使用。因此一些算法比如求平均数,在 process 里面很好实现,直接sum(values)/values.length,而在 Spark 中 func 可以实现sum(values),但不好实现sum(values)/values.length

性能优化的角度

  • MapReduce 的 shuffle 方式单一。
  • Spark 针对不同类型的操作、不同类型的参数,会使用不同的 shuffle write 方式。

猜你喜欢

转载自juejin.im/post/7108588104860041247