假设有Map Tasks=M,Reduce Tasks=R,则Spark会根据Map Tasks和Reduce Tasks数量生成ShuffleFile=M*R:
Spark1.2以后就有实现了两种Shuffle:Hash Shuffle和Sort Shuffle。
ShuffleMapTask的执行过程:先利用pipeline计算得到final RDD中对应的partition的records。没得到一个record就将其送到对应的bucket里,具体哪个bucket由partitioner.getPartition(record._1)决定。每个bucket里面的数据会不断被写到本地磁盘上,形成一个ShuffleBlockFile,或者简称FileSegment。之后的Reducer会去fetch属于自己的FileSegment,从而进入Shuffle Read阶段。
这是最基本的Hash Shuffle。每个Map Task都要将输出写到多个文件中。
假设有Map Tasks=M,Reduce Tasks=R,则Spark会根据Map Tasks和Reduce Tasks数量生成ShuffleFile=M*R。
这种实现就导致两个问题:
可以明显看出,在一个core上连续执行的ShuffleMapTask可以共用一个输出文件ShuffleFile。先执行完的ShuffleMapTask形成ShuffleBlock i(即bucket),后执行的ShuffleMapTask可以将输出数据直接追加到ShuffleBlock i后面,形成ShuffleBlock i',每个ShuffleBlock被称为FileSegment。下一个 stage 的 reducer 只需要 fetch 整个 ShuffleFile 就行了。这样,每个 worker 持有的文件数降为 cores * R。FileConsolidation 功能可以通过spark.shuffle.consolidateFiles=true来开启。
在Hadoop中,Shuffle阶段是要对数据进行排序的,即Reducer接收到的数据都是已经排好序的。在Spark1.2之后,也提供了Sort Shuffle。
在Sort Shuffle中,每个MapTask会生成一个ShuffleFile和一个IndexFile。
val partitionLengths = sorter.writePartitionedFile(blockId, context, outputFile) shuffleBlockResolver.writeIndexFile(dep.shuffleId, mapId, partitionLengths)
// Let the user specify short names for shuffle managers val shortShuffleMgrNames = Map( "hash" -> "org.apache.spark.shuffle.hash.HashShuffleManager", "sort" -> "org.apache.spark.shuffle.sort.SortShuffleManager", "tungsten-sort" -> "org.apache.spark.shuffle.unsafe.UnsafeShuffleManager") val shuffleMgrName = conf.get("spark.shuffle.manager", "sort") val shuffleMgrClass = shortShuffleMgrNames.getOrElse(shuffleMgrName.toLowerCase, shuffleMgrName) val shuffleManager = instantiateClass[ShuffleManager](shuffleMgrClass)