Spark之shuffle
什么是shuffle
Spark中的shuffle是指将不同的key重新进行分配的一个中间过程,首先Spark有3种类型的算子会产生shuffle:
重新分区操作,例如:repartition&coalesce
RDD的join操作, 例如:cogroup&join
bykey操作,例如:reduceByKey…
为什么会产生shuffle,shuffle中具体发生了什么事,我们通过reduceByKey()
算子进行分析
首先我们写一段简单的代码:
//假如source的分区数为1,在source的时候指定
val rdd: RDD[(String, Int)] = source.flatMap(_.split("\\s"))
.filter(!_.isEmpty) //1 to 1
.map((_, 1)) //1 to 1
.reduceByKey(_ + _, 2) // n to 1\ 1 to n \ n to n
//rdd的分区数为2
在以上代码中,在reduceByKey的时候,相当于把之前source 1个分区的数据分成2份到rdd的2个分区,在这个重新分配的过程中,source的单个分区的数据会交给不同的task进行处理,这里根据血缘关系,就有以下推理:
# 1.source为父RDD
# 2.rdd为子RDD
# 3.rdd是依赖于source,当执行reduce操作时,source的单个分区中的不同key的数据会分散到rdd的不同分区中,source=>rdd数据分区重新分配的处理过程就可以理解成shuffle
# 4.rdd与source之间的依赖就是宽依赖,用来切分stages(taskSets)
shuffle的性能影响
shuffle是一个及其昂贵的操作过程,虽然没有mapred的shuffle那么昂贵,但是还是会涉及到:磁盘IO、数据序列化、网络IO等相关的性能消耗,为了组织shuffle的数据,spark会生成 task-sets(map tasks去组织数据,reduce tasks去聚合数据,这个与MR中的性质其实是异曲同工的)
shuffle过程
在spark1.6之前是有HashShuffle的,但是在1.6之后默认的shuffle方式改成了SortedShuffle
sorted shuffle是什么
shuffle是map操作与reduce操作之间的中间操作,shuffle过程中,map tasks组织的数据将会缓存在execute的缓存buffer内存中,当达到buffer阈值时,这些数据会基于目标分区的个数(即reduce中指定的并行度)进行排序并且写入到单个文件中,在reduce端,tasks读取对应hash值的key的数据的数据块(sorted blocks)。
一、shuffle操作会消耗大量的堆内存,因为在转移数据前后会雇佣对应的内存数据结构组织并缓存数据,具体来讲,
reduceByKey aggregateBykey
会在map端创建这样的数据结构,bykey
操作创建转换之后的数据结构在reduce端,当数据量超过了缓存内容的阈值,那么会将这些数据或者表溢写到磁盘,从而产生额外的IO开销、增加垃圾回收。
二、shuffle还会在磁盘上产生大量的中间文件,从spark1.3开始,将保留对应的文件直到对应的RDD不再被引用并且进行GC之后才会删除,这样做时为了在追溯血缘关系时无需重新创建shuffle文件,如果应用程序保留了对这些RDD的引用,或者young GC不经常启动,很有可能造成full GC,造成stop the world的局面,这意味着长时间运行的Spark作业可能会占用大量的磁盘空间。临时的文件存储目录可以通过
spark.local.dir
在sparkconf进行指定
shuffle行为的配置指南可以参考:spark-shuffle配置指南
//TODO 今后会持续更新,并配上相应的图解