Spark Shuffle 的过程

shuffle 的核心要点

shuffleMapStage 与 ResultStage

在划分 stage 时,最后一个stage 成为 finalStage,它本质上是 resultstage 对象,前面的所有的 stage 被称为 shuffleMapStage。
shuffleMapStage 的结束伴随着 shuffle 文件的写磁盘。
ResultStage 基本上对应着代码中的 action 算子,即是将一个函数应用在 rdd 的各个的数据集上,意味着一个 job 的结束。

基本的流程: 在DAG 阶段以 shuffle 为界,划分 stage,上游的stage 做map task,每个 map task 将计算结果数据分成多份,每一份对应到下游 stage 的每个 partition 中,并将其临时写到磁盘,该过程叫做 shuffle write;下游 stage 做reduce task,每个 reduce task 通过网络拉取上游stage 中所有map task 的指定分区结果数据,该过程叫做shuffle read,最后完成reduce 的业务逻辑。
在 Spark 1.x 有 hash shuffle 和 sort shuffle 两种,到 spark 2.x 版本之后,hash shuffle 就推出了舞台,只剩下 Sort shuffle.
目前 Sort Based shuffle 的 writer 分为三种,BypassMergeSortshuffleWriter、SortShuffleWriter、和unsafeShuffleWriter。

先了解一下 hashShuffle
1. 未经优化的 hashshuffle
shuffle write 阶段,主要就是一个 stage 结束计算之后,为了下一个 stage 可以执行shuffle 类的算子(比如reducebyKey),而将每个 task 处理的数据按 key 进行 “划分”(所谓划分,就是对相同的 Key 执行hash 算法, 从而将相同的 key 都写入同一个磁盘文件中,而每一个磁盘文件都只属于下游 stage 的一个task),在将数据写入磁盘之前,会先将数据写入到内存缓冲中,当内存缓冲填满之后,才会溢写到磁盘文件中。

下一个 stage 的task 有多少个,当前的stage 的每个task 就要创建多少分磁盘文件(比如下一个 stage 总共有 100 个task,那么当前 stage 的每个 task 都要创建 100 份磁盘文件)。由此可见,未经优化的 shuffle write 操作所产生的磁盘文件的数量是惊人的,会产大量的磁盘小文件。
在这里插入图片描述

2.经过优化的hashShuffle

未经优化的 hashshuffle 是以 maptask 以维度,每一个 maptask 会产生和下一阶段 reduce task 数目相同的磁盘文件,而优化过后,以 cpu 为维度,每个 cpu 的会生成 reduce task 个数的磁盘文件,通过利用文件的复用,减少了磁盘文件的产生。
未经优化磁盘文件数: N * M (N是 maptask 个数,M 是reduce task 个数)
优化过后磁盘文件数: C * M(C:是cpu 个数,通常每个运行的 excutor 个数是 60 -100 个,每个excutor 分配的cpu 个数是 6-10 个)
在这里插入图片描述

再说 Spark 2.x 的 Sort shuffle.

目前 Sort Based shuffle 的 writer 分为三种,BypassMergeSortshuffleWriter、SortShuffleWriter、和unsafeShuffleWriter。

1.Bypass MergeSortShuffleWriter
与 hashshuffle 中的 hashshuffle 的 writer 基本一致,唯一区别在于,map端的多个输出文件会被汇总为同一个文件,会生成一个索引文件,索引文件是为了索引到每个分区的起始地址,可以随机 access 某个 partition 的所有数据。

但是需要注意,这种方式不宜有太多分区(不超过200),因为过程中会并发打开所有分区对应的临时文件,会对文件系统造成过大的压力。这种模式下为了减少IO次数,会采用 buffer ,但是 buffer 的大小默认为 32k,当然这个大小是可以通过spark.shuffle.file.buffer参数自定义配置的。
2、SortShuffleWriter:会对分区排序,或则进行全局排序
处理步骤:

  1. 使用 PartitionedAppendOnlyMap 或则 PartitionedPairBuffer 在内存中进行排序,排序的 Key 是(partitionId,hash(key))这样一个元组。
  2. 如果超过内存阈值,就spill 到一个文件中,这个文件中的元素也是有序的,首先是按照 partitionId 进行排序,如果 PartitionId 相同,再根据 Hash(key) 进行比较排序。
  3. 如果需要输出全局有序的文件的时候,就需要对之前的所有的输出文件和当前内存中的数据结构进行 merge sort,实现全局有序。
  4. 最终读取的时候,从整个全局 merge 后的读取迭代器中读取的数据,就是按照 parttionId 从小到大排序的数据,读取过程中按照分区分段,并且记录每个分区文件的起始写入位置,把这些位置数据写入到索引文件中。

SortShuffleWriter 中使用 ExternalSorter 来对内存中的数据进行排序,ExternalSorter内部维护了两个集合PartitionedAppendOnlyMap、PartitionedPairBuffer,两者的区别如下
在这里插入图片描述
3. UnsafeShuffleWriter: 优化部分是 shuffle write 进行序列化写入过程中,直接对二进制进行排序,减少了内存消耗和 GC 的开销,最终只是 partition 级别的排序。但是这种模式也有一定限制:shuffle 数量有限制,而且不能带有聚合函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SparkShuffle过程是指在数据处理过程中,将数据重新分区和排序的过程。它是Spark中非常重要的一个操作,用于将数据从一个RDD的分区传输到另一个RDD的分区。 SparkShuffle过程包括两个阶段:Map阶段和Reduce阶段。 在Map阶段,每个Executor上的任务(Task)会将输入数据根据指定的分区函数进行分区,并将分区后的数据写入磁盘上的.data文件中。同时,还会生成一个.index文件,用于记录每个分区的数据在.data文件中的位置信息。 在Reduce阶段,Spark会根据分区函数将数据重新分配到不同的Executor上的任务中。每个任务会读取自己负责的分区数据,并进行合并、排序等操作,最终生成最终结果。 SparkShuffle过程可以使用不同的策略来实现,其中包括BypassMergeSortShuffleWriter、SortShuffleWriter和UnsafeSortShuffleWriter等。 BypassMergeSortShuffleWriter是一种优化策略,它会尽量减少数据的复制和排序操作,提高Shuffle的性能。 SortShuffleWriter是一种常用的策略,它会将数据写入磁盘,并使用外部排序算法对数据进行排序。 UnsafeSortShuffleWriter是一种更高效的策略,它使用了内存进行排序,减少了磁盘IO的开销。 下面是一个示例代码,演示了SparkShuffle过程: ```scala val inputRDD = sc.parallelize(List(("apple", 1), ("banana", 2), ("apple", 3), ("banana", 4))) val shuffledRDD = inputRDD.groupByKey() val resultRDD = shuffledRDD.mapValues(_.sum()) resultRDD.collect().foreach(println) ``` 这段代码首先创建了一个输入RDD,其中包含了一些键值对数据。然后使用groupByKey()函数对数据进行分组,生成一个ShuffledRDD。最后使用mapValues()函数对每个分组进行求和操作,得到最终结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值