shuffle:groupByKey、reduceByKey、countByKey、部分join
遇到宽依赖就会产生shuffle,切分出新的stage。
数据倾斜:相同key的数据会分发到同一个task中执行。
调优:代码、资源、skew、shuffle
shuffle的类型
hash:HashShuffleManager ( < spark1.2)
sort:SortShuffleManager ( > spark1.2)
tungsten-sort:SortShuffleManager ( > spark1.5)
HashShuffleManager
每一个map task要为每个reduce task创建“东西”(内存(bucket)、file)。
产生的问题:
1)产生过多的file:M * R
spark job的maptask和reducetask个数都是比较大的,所以中间产生的磁盘文件数量是非常惊人的。
2)耗费过多的内存空间
每个maptask需要开启R个bucket。
由于内存可以复用,同时存在的bucket数量:R * core
bucket大小:spark.shuffle.file.buffer,默认32K
HashShuffleManager优化
map端输出文件合并:一个core上连续执行的shufflemaptask可以共用一个输出文件。
产生的file数量:R * core
只需要配置:spark.shuffle.consolidateFiles (1.2才有)
SortShuffleManager
SortShuffleManager的运行机制主要分成两种,一种是普通运行机制,另一种是bypass运行机制。当shuffle map task的数量小于等于spark.shuffle.sort.bypassMergeThreshold参数的值时(默认为200),就会启用bypass机制。
普通运行机制
在该模式下,Shuffle Write 阶段会将数据写入一个内存的数据结构中,此时根据不同的算子会有不同的数据结构。比如是 reduceByKey 这种聚合类的 shuffle 算子,会选用 Map 数据结构,一遍用 Map 进行聚合,一遍写入内存;如果是 join 相关的普通 shuffle 算子的话,会用 Array 数据结构,直接写入内存。当内存达到临界阈值之后,会将内存中的数据进行排序,然后分批次写入内存缓冲流 (默认每批次有 1W 条数据),当缓冲流溢满之后一次性写入磁盘。
此时也会生成大批量的文件,最后会将之前所有的临时磁盘文件进行合并,这就是 merge 过程 (就是将所有的临时磁盘文件中的数据读取出来,然后依次写入最终的文件中)。每个 task 最终会生成一份磁盘文件和一份索引文件,索引文件中标识了下游每个 task 数据在文件中的 start offset 和 end offset。
bypass 机制
触发该机制的条件:
- shuffle reduce 端的 task 数量小于 spark.shuffle.sort.bypassMergeThreshold 参数值(200);
- 不是聚合类的shuffle算子(比如reduceByKey);
该机制下,当前 stage 的每个 task 会将数据的 key 进行 hash,然后将相同 hash 的 key 所对应的数据写入到同一个内存缓冲区,缓冲写满后会溢写到磁盘文件(这里和 HashShuffleManager一致),然后会进入 merge 阶段,将所有的磁盘文件合并成一个磁盘文件,并创建一个索引文件。
相比较于普通机制,这里有两个地方不同:
- 将数据写入内存时候,普通模式是将数据写入 Map 或者 Array 这样的内存数据结构中,这里是根据 key 的 Hash 值写入内存;
- 该模式下在写入磁盘之前不会排序。