spark shuffle

sparkshuffle主要部分就是shuffleWrite 和 shuffleReader.

 

大致流程

spark通过宽依赖划分stage,如果是宽依赖就需要进行shuffle操作,上游stage的shufflemaptask进行shuffleWrite,上游的write操作做的最重要的操作其实就是分区,元数据根据MapOutputTrackerWorker汇报给driver端MapOutputTrackerMaster,下游stage去driver获取元数据,进行shuffleRead,拉取上游stagemaptask存储的数据。 shuffle会通过shuffleMapTask进行shuffle Write操作,然后通过下游stage的shuffleReduceTask进行shuffle Read操作。 所以shuffle操作主要发生在runTask中。

早期hashShuffle

早期hashShuffle为每个reduceTask建立bucket缓存,每个缓存对应着生成一个文件也就是会生成n个mapTask* m个reduceTask的文件个数。后来也进行过优化根据核数优化了小文件的产生数量。 缺点就是: 1.不具有排序的功能。 2.只是单纯将数据刷到文件,其次文件数过多。 3.大量GC。堆内存中会存储大量的对象。

所以在1.6之后推出了sort Based shuffle.

 

目前的shuffleWrite 有3种实现,常用的有两种,两种区别在于是否map排序,而reader只有一种实现。

shuffleWrite 及写流程

会有缓存,排序,溢写,合并几个操作。 缓存 缓存会针对不同的聚合类算子使用不同的数据结构。默认内存数据结构5M. 比如:reduceBykey算子就会使用map数据结构缓存,join算子就会使用buffer(基于数组)数据结构缓存。 为什么sortShuffle不同聚合算子使用不同的数据结构缓存呢? 因为主要看是否用到map端的combine操作。比如reduceByKey聚合会先在map端进行聚合,然后到reduce端再次聚合。使用map数据结构方便map端相同key的combine操作。而join不需要map端的combine,所以直接使用buffer. shuffle中会有定时器来检查内存数据结构的大小。如果内存不够,会根据公式申请额外的内存(当前申请内存数据结构存储的数据*2-内存数据结构的设定值的内存大小空间)。每次达到阈值就会溢写磁盘。然后清空内存数据结构。 排序 在溢写之前会先根据key对数据结构中的数据进行排序。 溢写 也是分批数据溢写到磁盘。先进行缓存在刷写磁盘。 合并 在每个task中每次缓存溢写都会生成多个临时文件,然后将多个临时文件合并成一个文件。也就是说每个task针对reduce端只会生成一个数据文件,并生成一份索引文件。索引文件标记了下游stage中各个task的数据在文件中的位置。 这里的合并过程和hbase多路归并差不多,每次只加载每个文件的头部部分数据,然后在内存排序。不断的比较加载输出。 而sortShuffle bypass机制是将排序过程省略掉了,为了更加提升性能。 BypassMergeSortShuffleWriter: 适合任务数据量小,map端没有聚合,数据量不大的场景。。因为该writer中间并并发打开多个文件。 SortShuffleWriter: 有聚合,适合数据量大的场合。上述流程即是 还有一种不怎么用。

 

shuffle Read

主要包括:寻址,拉取操作。 reduceTask会先寻找文件的地址 寻址 MapOutputTracker是Spark架构中的一个模块,是一个主从架构。管理磁盘小文件的地址。 其实driver端会有MapOutputTrackerMaster角色,每个executor有MapOutputTrackerWorker角色。是主从结构。 每次maptask执行完成之后,会将task的磁盘文件的地址通过executor上的MapOutputTrackerWorker上汇报给driver端的MapOutputTrackerMaster。 所以shuffleMapTask执行完毕,driver就会有所有shuffle磁盘文件的地址。 reduceTask执行前会通过所在executor的MapOutPutTrackerWorker向driver端的MapOutputTrackerMaster获取小文件地址。 拉取 拉取会从两个地方查找,一个是本地拉取,还有远程拉取。 然后通过blockManager去远程或者本地进行拉取。