这是我参与「第四届青训营 」笔记创作活动的的第5天
Shuffle概述
MapReduce概述

- 2004年谷歌发布了《MapReduce:Simplified Data Processing on LargeClusters》论文
- 在开源实现的MapReduce中,存在Map、Shuffle、Reduce三个阶段。
上图展示了一个典型的 mapreduce 过程。有一大堆数据,如果我们用单机去分析这些数据的话,可能一周或者一个月的时间都没有办法分析完。然后怎么办呢?我们把这些数据就拆分成若干的小份,分散在很多机器上去做并发处理。假设我把这些数据拆成单份只有 500 兆的数据。那在 1 在一个单机上去处理 500 兆的数据,是可以很快的处理完的。然后第一步的这个分布式的处理过程我们叫做map,然后因为每份数据都可能包含任意的颜色,所以我们需要对不同颜色的数据分别处理。在所有这些任务完成处理完成之后,为了把所有把颜色归并到一起,我们就需要移动数据。然后再把相同颜色的数据移动到一起之后,又可以把这些数据分散在很多机器上去做并发处理了。
第二步,这个分成单份数据做的这个处理我们叫做 reduce 在中间移动数据的这一步我们就是 shuffle,值得一提的是在这个 map 和 reduce 的过程中被分成一块单独去处理的这个数据,我们一般把它叫做partition,它是在 map 阶段的基础上进行数据移动。然后我们把每一个 map 处理好的数据再去做移动,然后把相同颜色的数据放到一起,为后续的 reduce 阶段做准备。第三个阶段就是 reduce 阶段。这个时候我们已经把相同颜色的数据放到一起了,就是在 reduce 阶段对移动后的数据继续进行处理。然后这一步这一步依然是在单件上处理一小份数据,最终我们可以拿到就是不同颜色的一个。 sum 值。
为什么Shuffle对性能非常重要
-
MR次网络连接
-
大量的数据移动
-
数据丢失风险
数据在移动的过程中以及在计算的过程中都是有可能会丢失的,就是它是有丢失风险的。然后一旦这些数据丢失,我们就面临数据的重算。
-
可能存在大量的排序操作
我们需要在 map 阶段把不同颜色的数据区分开放到不同的位置,我们就需要对这些数据以颜色来进行排序。
-
大量的数据序列化、反序列化操作
要把 Java 或者是某一个内存中的一个直观的数据转化成一个二进制的数据流,然后放到文件中,然后我们再从文件中把它读到内存里面来,然后变成一重新变成一个对象。这个是序列化和反序列化操作,也会消耗大量的 CPU
-
数据压缩
如果我们的数据量非常大,我们在存储的过程中可能还涉及到数据的压缩与解压缩,这个也会消耗大量的 CPU
在大数据场景下,数据shuffle表示了不同分区数据交换的过程,不同的shuffle策略性能差异较大。 目前在各个引擎中shuffle都是优化的重点,在spark框架中, shuffle是支撑spark进行大规模复杂 数据处理的基石。
Shuffle算子
Spark中会产生Shuffle的算子大概可以分为4类

Spark中队shuffle的抽象:宽依赖、窄依赖

- 窄依赖:父RDD的每个分片至多被子RDD中的-一个分片所依赖
- 宽依赖:父RDD中的分片可能被子RDD中的多个分片所依赖
算子内部的依赖关系
- ShuffleDependency
- CoGroupedRDD
- Cogroup
- fullOuterJoin、rightOuterJoin、 leftOuterJoin
- join
- ShuffledRDD
- combineByKeyWithClassTag
- combineByKey
- reduceByKey
- Coalesce
- SortByKey
- sortBy
- combineByKeyWithClassTag
- Cogroup
- CoGroupedRDD
Shuffle Dependency构造
- A single key-value pair RDD, i.e. RDD[Product2[K, V],
- Partitioner (available as partitioner property),
- Serializer,
- Optional key ordering (of Scala's scala.math.Ordering type),
- Optional Aggregator,
- mapSideCombine flag which is disabled (i.e. false) by default.
Partition


两个接口
- numberPartitions
- getPartition
经典实现
- HashPartitioner
Aggregator
它是在进行 Shark 的时候一个非常重要的性能优化器。
- createCombiner :只有一个value的时候初始化的方法
- mergeValue :合并一个value到Aggregator中
- mergeCombiners :合并两个Aggregator
Shuffle过程
发展历史
- Spark 0.8及以前Hash Based Shuffle
- Spark 0.8. 1为Hash Based Shuffle引|入File Consolidation机制
- Spark 0.9引入ExternalAppendOnlyMap
- Spark 1.1引入Sort Based Shuffle,但默认仍为Hash Based Shuffle
- Spark 1.2默认的Shuffle方式改为Sort Based Shuffle
- Spark 1.4引入Tungsten-Sort Based Shuffle
- Spark 1.6 Tungsten-Sort Based Shuffle并入Sort Based Shuffle
- Spark 2.0 Hash Based Shuffle退出历史舞台
Hash Shuffle——写数据

他是把不同的 partition 直接就写到不同的文件中,每一个 map task 都会给每一个 partition 创建一个 buffer 写满了之后就 Flash 到磁盘里面。最终会生成 M 乘以 R 个文件。
存在问题:
这样的话生成的文件太多了,对文件系统造成的压力太大了。而且不光它是生成的文件多,它实际上在运行过程中同时打开的文件也会非常多。然后我们知道打开一个文件本身也是需要消耗资源的。
Hash Shuffle——写数据优化

每个partition会映射到一个文件片段
Sort Shuffle——写数据

每个task生成-个包含所有pariton数据的文件
Shuffle过程的触发流程

Shuffle Handle的创建

Register Shuffle时做的最重要的事情是根据不同条件创建不同的shuffle Handle
Handle与Writer的对应关系

BypassMergeShuffleWriter

- 不需要排序,节省时间
- 写操作的时候会打开大量文件
- 类似于Hash Shuffle
UnsafeShuffleWriter

- 使用类似内存页储存序列化数据
- 数据写入后不再反序列化

- 只根据partition 排序Long Array
- 数据不移动
SortShuffleWriter

- 支持combine
- 需要combine时使用PartitionedAppendOnlyMap ,本
- 质是个HashTable
- 不需要combine时PartitionedPairBuffer本质是个array