Shuffle 概述

影响Spark性能的大BOSS就是shuffle,因为该环节包含了大量的磁盘IO、序列化、网络数据传输等操作。

因此,如果要让作业的性能更上一层楼,就有必要对 shuffle 过程进行调优。

当然,影响 Spark 性能的还有代码开发、参数设置数以及数据倾斜的解决等,甚至这部分才是大头,shuffle 调优只能在整个 Spark 的性能调优中占到一小部分而已。

所以写好一个优秀高效的代码才是关键。

shuffle 调优 只是锦上添花而已。


未经优化的HashShuffleManager

这是spark1.2版本之前,最早使用的shuffle方法,这种shuffle方法不要使用,只是用来对比改进后的shuffle方法。

上游每个task 都输出下游task个数的结果文件,下游每个task去上游task输出的结果文件中获取对应自己的文件。

缺点:生成文件个数过多,生成和传输文件数量等于 上游task数量 * 下游task数量 个文件。

对应spark参数如下:

参数
spark.shuffle.manager hash
spark.shuffle.consolidateFiles false

经过优化以后的HashShufferManager

上游1个Executor所有task顺序输出下游task个数的结果文件,下游每个task去上游task输出的结果文件中获取对应自己的。

这个过程中有一个shuffleFileGroup 的概念,每个 shuffleFileGroup 会对应一批磁盘文件,磁盘文件的数量与下游 stage 的 task 数量是相同的。

一个 Executor 上有多少个 CPU core,就可以并行执行多少个task。而第一批并行执行的每个 task 都会创建一个shuffleFileGroup,并将数据写入对应的磁盘文件内。

结合下图可知,优化后的HashShufferManager减少了中间文件输出,生成和传输 上游executor_num * 下游task数量 个文件

对应spark参数如下:

参数
spark.shuffle.manager hash
spark.shuffle.consolidateFiles true


SortShuffleManager 运行原理

SortShuffleManager 有两种的运行机制:

普通运行机制

bypass运行机制

当 shuffle read task 的数量大于 spark.shuffle.sort.bypassMergeThreshold 参数的值时(默认 200),启用 bypass 机制。

普通运行机制

触发条件:shuffle read task 的数量大于 spark.shuffle.sort.bypassMergeThreshold(默认200)

原理及流程:

  1. 数据会先写入一个内存数据结构中,此时根据不同的 shuffle 算子,可能选用不同的数据结构(Map or Array )。
  2. 如果是 reduceByKey 这种聚合类的 shuffle 算子,那么会选用 Map 数据结构,一边通过 Map进行聚合,一边写入内存(这也是为什么某些情况下聚合类算子选用reduceByKey 替换groupbykey);
  3. 如果是普通的 shuffle算子如join,count等,那么会选用Array数据结构,直接写入内存。
  4. 然后,每写一条数据进入内存数据结构之后如果达到了某个临界阈值,那么就会尝试将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构。
  5. 在溢写到磁盘文件之前,会先根据 key对内存数据结构中已有的数据进行排序。
  6. 排序过后,会分批将数据写入磁盘文件。默认的 batch数量是10000条,也就是说,排序好的数据,会以每批 1万条数据的形式通过 Java 的 BufferedOutputStream写入磁盘文件。
  7. 一个 task 将所有数据写入内存数据结构的过程中,会发生多次磁盘溢写操作,产生多个临时文件。最后会将之前所有的临时磁盘文件都进行合并,这就是 merge 过程,此时会将之前所有临时磁盘文件中的数据读取出来,生成 1个文件+索引文件(标识了下游各个 task 的数据在文件中的 start offset 与 end offset)。

注:BufferedOutputStream 是 Java的缓冲输出流,首先会将数据缓冲在内存中,当内存缓冲满溢之后再一次写入磁盘文件中,这样可以减少磁盘 IO 次数,提升性能。

最终产生的文件数量等于上游task的数量,如上游task 有100个,下有task200个,也只会产生100个文件

对应spark参数如下:

参数
spark.shuffle.manager sort
spark.shuffle.sort.bypassMergeThreshold 默认200

bypass 运行机制

触发条件如下:

  1. 没有map端聚合(比如 reduceByKey)。
  2. 最多有spark.shuffle.sort.bypassMergeThreshold reduce分区。

原理及流程:

  1. 上游task 会为每个下游 task 都创建一个内存缓冲,并根据 key 的 hash 值写入对应的缓冲区。
  2. 缓冲区满之后溢写到磁盘文件的。
  3. 最后,将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。

对比未经优化的 HashShuffleManager:

其实前面的步骤和未经优化的 HashShuffleManager是一摸一样额,只是最后多了一了merge的操作,产生的文件包括一个盘文件和一个索引文件。

最终磁盘文件的数量等于上游task的数量


shuffle 相关参数调优

参数来源:spark 官方文档

此处只选择部分参数解析

spark.shuffle.file.buffer:32k

释义:在shuffle write task 通过 BufferedOutputStream将数据写到磁盘文件之前,会先写入 buffer 缓冲中,待缓冲写满之后,才会溢写到磁盘。次参数控制改缓冲区的大小。

建议:资源允许的情况下,可以设置的大一点,可以减少减少磁盘 IO 次数,提升性能。

spark.reducer.maxSizeInFlight:48m

释义:从每个reduce任务同时获取的map输出的最大大小,由于每个输出都需要创建一个缓冲区来接收它,因此每个reduce任务的内存开销都是固定的,所以要保持较小的内存,除非您有大量的内存。。

建议:资源允许的情况下,可以设置的大一点,可以减少网络传输的次数,提升性能。

spark.shuffle.io.maxRetries:3

释义:(Netty only)shuffle read task 从 shuffle write task 所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的,如果设置为非零值n,则会自动重试n次由于io相关异常而失败的获取。

这种重试逻辑有助于在面对长时间GC暂停或瞬态网络连接问题时稳定大型改组。

建议:对于超大型的任务,建议调大该参数(比如30+)

spark.shuffle.io.retryWait:5s

释义:(Netty only)两次读取重试之间需要等待多长时间。默认情况下,重试导致的最大延迟为15秒,计算为maxRetries * retryWait。

建议:加大间隔时长(比如 60s),以增加 shuffle 操作的稳定性,对应的可能回导致任务执行时间加长。

spark.memory.useLegacyMode:false

释义:是否启用Spark 1.5及以前使用的遗留内存管理模式。遗留模式严格地将堆空间划分为固定大小的区域,如果应用程序没有进行调优,可能会导致过度溢出。

只有设置为true,会读取下列三个已废弃的内存部分配置:

spark.shuffle.memoryFraction
spark.storage.memoryFraction
spark.storage.unrollFraction

spark.shuffle.memoryFraction:0.2

释义:(不推荐)在spark.memory.useLegacyMode=true时才会启用,shuffle期间Executor 内存中用于聚合和组合的Java堆所占的比例,在任何给定时间,用于shuffle的所有内存映射的集合大小都受此限制,超过此限制,内容将开始溢出到磁盘。

建议:内存充足情况下,而且很少使用持久化操作,且溢出到磁盘频繁,建议调高这个比例,给 shuffle read 的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。

spark.shuffle.manager:sort

释义:该参数用于设置 ShuffleManager 的类型。Spark 1.5以后,有三个可选项:hash、sort 和tungsten-sort(这个就是所谓的钨丝计划,貌似出了很多bug,玩脱了,至少我没怎么用)。HashShuffleManager 是 Spark 1.2 以前的默认选项,但是 Spark 1.2以及之后的版本默认都是 SortShuffleManager 了。

建议:维持原样,通过后面的两个参数控制是否排序。

spark.shuffle.sort.bypassMergeThreshold:200

释义:(高级)在基于排序的shuffle manager 中,如果没有map端聚合并且最多有这么多reduce分区,则避免合并排序数据。

建议:如果shuffle 的确不需要排序操作,可以将这个参数调大一些,大于 shuffle read task 的数量。会自动启用 bypass 机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件(貌似不排序也没少多少...但是时间会比排序少很多就是了)。

spark.shuffle.consolidateFiles:false

释义:如果设置为 true,那么就会开启 consolidate 机制,会大幅度合并 shuffle write 的输出文件,对于 shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘 IO 开销,提升性能。

建议:如果确实不需要排序排序机制,那么设置spark.shffle.manager=hash,测试发现性能比开启了 bypass 机制的 SortShuffleManager要高(猜测可能是多了merge,和索引部分的操作,不过没有验证过),


Spark 调优之ShuffleManager、Shuffle的更多相关文章

  1. 【Spark调优】Shuffle原理理解与参数调优

    [生产实践经验] 生产实践中的切身体会是:影响Spark性能的大BOSS就是shuffle,抓住并解决shuffle这个主要原因,事半功倍. [Shuffle原理学习笔记] 1.未经优化的HashSh ...

  2. spark 调优概述

    分为几个部分: 开发调优.资源调优.数据倾斜调优.shuffle调优 开发调优: 主要包括这几个方面 RDD lineage设计.算子的合理使用.特殊操作的优化等 避免创建重复的RDD,尽可能复用同一 ...

  3. 【Spark学习】Apache Spark调优

    Spark版本:1.1.0 本文系以开源中国社区的译文为基础,结合官方文档翻译修订而来,转载请注明以下链接: http://www.cnblogs.com/zhangningbo/p/4117981. ...

  4. 【Spark调优】小表join大表数据倾斜解决方案

    [使用场景] 对RDD使用join类操作,或者是在Spark SQL中使用join语句时,而且join操作中的一个RDD或表的数据量比较小(例如几百MB或者1~2GB),比较适用此方案. [解决方案] ...

  5. 【Spark调优】数据倾斜及排查

    [数据倾斜及调优概述] 大数据分布式计算中一个常见的棘手问题——数据倾斜: 在进行shuffle的时候,必须将各个节点上相同的key拉取到某个节点上的一个task来进行处理,比如按照key进行聚合或j ...

  6. 【翻译】Spark 调优 (Tuning Spark) 中文版

    由于Spark自己的调优guidance已经覆盖了很多很有价值的点,因此这里直接翻译一份过来.也作为一个积累. Spark 调优 (Tuning Spark) 由于大多数Spark计算任务是在内存中运 ...

  7. 【Spark调优】:结合业务场景,优选高性能算子

    聚合操作使用reduceByKey/aggregateByKey替代groupByKey 参见我的这篇博客说明 [Spark调优]:如果实在要shuffle,使用map侧预聚合的算子 内存充足前提下使 ...

  8. Spark调优_性能调优(一)

    总结一下spark的调优方案--性能调优: 一.调节并行度 1.性能上的调优主要注重一下几点: Excutor的数量 每个Excutor所分配的CPU的数量 每个Excutor所能分配的内存量 Dri ...

  9. Spark 调优(转)

    Spark 调优 返回原文英文原文:Tuning Spark Because of the in-memory nature of most Spark computations, Spark pro ...

随机推荐

  1. 【Flutter学习】基本组件之基本表单组件

    一,概述 表单时一个包含表单元素的区域. 表单元素允许用户输入内容,比如文本域,下拉列表,单选框,复选框等.常见的应用场景有:登录,注册,输入信息等. 表单里有两个重要的组件: Form:用来做整个表 ...

  2. go语言将函数作为参数传递

    Go语言函数作为参数传递,目前给我的感觉几乎和C/C++一致.非常的灵活. import "fmt" import "time" func goFunc1(f  ...

  3. Serializable读写类操作

    import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStrea ...

  4. 网络数据包最大长度 MTU 分片 转发https://blog.csdn.net/singular2611/article/details/52513406

    1.数据链路层对数据帧的长度都有一个限制,也就是链路层所能承受的最大数据长度,这个值称为最大传输单元,即MTU.以以太网为例,这个值通常是1500字节. 2.对于IP数据包来讲,也有一个长度,在IP包 ...

  5. 前端(七)—— 盒模型之display、overflow、隐藏、border、margin、样式支持,层级结构

    display.overflow.隐藏.border.margin.样式支持,层级结构 一.盒模型之display 1.三种样式 block 块 inline 内联/行内 inline-block 内 ...

  6. 20140730 word标题样式 数组

    1.word 标题四, 右键更新 自己也可以新建标题样式 2.数组 连续内存,空间复杂度高(即使数组存在一个元素,占据的空间的大小不变),时间复杂度低(0(1)访问),内存分配一次性完成

  7. python3 递归函数return返回None

    今天写了一个函数,执行之后打印出来的结果是None,不明白,之后百度了一下,这里记一下过程,免得之后再踩坑 #!/usr/bin/python3# -*- coding:utf-8 -*- def b ...

  8. 从内部重启python脚本

    我有一个基于python的GTK应用程序,可以加载几个模块.它是从(linux)终端运行的,如下所示: ./myscript.py --some-flag setting 用户可以从程序中下载(使用G ...

  9. 最长递增子序列nlogn的做法

    费了好大劲写完的  用线段树维护的 nlogn的做法再看了一下 大神们写的 nlogn  额差的好远我写的又多又慢  大神们写的又少又快时间  空间  代码量 哪个都赶不上大佬们的代码 //这是我写的 ...

  10. shell实现批量创建交叉编译工具软链接

    在学习嵌入式过程中,常常用到交叉编译工具,而原本的交叉工具链比较长,不利于记忆以及使用, 解压后的交叉编译工具链如下图所示 为了更好的使用交叉编译工具与其他开发者保持一致,经常需要用到软链接. NAM ...