随着Spark的逐渐成熟完善, 愈来愈多的可配置参数被添加到Spark中来, 在Spark的官方文档http://spark.apache.org/docs/latest/configuration.html 中提供了这些可配置参数中至关大一部分的说明.html
可是文档的更新老是落后于代码的开发的, 还有一些配置参数没有来得及被添加到这个文档中, 最重要的是在这个文档中,对于许多的参数也只能简单的介绍它所表明的内容的字面含义, 若是没有必定的实践基础或者对其背后原理的理解, 每每没法真正理解该如何针对具体应用场合进行合理配置。
算法
本文试图经过阐述这其中部分参数的工做原理和配置思路, 和你们一块儿探讨一下如何根据实际场合对Spark进行配置优化。须要注意的是,理论上,没有绝对正确的配置(不然也就不须要对应的配置参数了,Spark框架内部直接写死就行了),因此请结合本身的实际状况,辩证的看下面的内容。apache
因为本文主要针对和性能相关的一些配置参数进行阐述,因此基本不会覆盖其它和性能没有太多关系的配置参数。网络
因为篇幅较长,因此在这里分篇组织,若是要看最新完整的网页版内容,能够戳这里:http://spark-config.readthedocs.org/,主要是便于更新内容数据结构
Shuffle 相关并发
Shuffle操做大概是对Spark性能影响最大的步骤之一(由于可能涉及到排序,磁盘IO,网络IO等众多CPU或IO密集的操做),这也是为何在Spark 1.1的代码中对整个Shuffle框架代码进行了重构,将Shuffle相关读写操做抽象封装到Pluggable的Shuffle Manager中,便于试验和实现不一样的Shuffle功能模块。例如为了解决Hash Based的Shuffle Manager在文件读写效率方面的问题而实现的Sort Base的Shuffle Manager。
框架
spark.shuffle.manager性能
用来配置所使用的Shuffle Manager,目前可选的Shuffle Manager包括默认的org.apache.spark.shuffle.sort.HashShuffleManager(配置参数值为hash)和新的org.apache.spark.shuffle.sort.SortShuffleManager(配置参数值为sort)。测试
这两个ShuffleManager如何选择呢,首先须要了解他们在实现方式上的区别。
优化
HashShuffleManager,故名思义也就是在Shuffle的过程当中写数据时不作排序操做,只是将数据根据Hash的结果,将各个Reduce分区的数据写到各自的磁盘文件中。带来的问题就是若是Reduce分区的数量比较大的话,将会产生大量的磁盘文件。若是文件数量特别巨大,对文件读写的性能会带来比较大的影响,此外因为同时打开的文件句柄数量众多,序列化,以及压缩等操做须要分配的临时内存空间也可能会迅速膨胀到没法接受的地步,对内存的使用和GC带来很大的压力,在Executor内存比较小的状况下尤其突出,例如Spark on Yarn模式。
SortShuffleManager,是1.1版本以后实现的一个试验性(也就是一些功能和接口还在开发演变中)的ShuffleManager,它在写入分区数据的时候,首先会根据实际状况对数据采用不一样的方式进行排序操做,底线是至少按照Reduce分区Partition进行排序,这样来至于同一个Map任务Shuffle到不一样的Reduce分区中去的全部数据均可以写入到同一个外部磁盘文件中去,用简单的Offset标志不一样Reduce分区的数据在这个文件中的偏移量。这样一个Map任务就只须要生成一个shuffle文件,从而避免了上述HashShuffleManager可能遇到的文件数量巨大的问题
二者的性能比较,取决于内存,排序,文件操做等因素的综合影响。
对于不须要进行排序的Shuffle操做来讲,如repartition等,若是文件数量不是特别巨大,HashShuffleManager面临的内存问题不大,而SortShuffleManager须要额外的根据Partition进行排序,显然HashShuffleManager的效率会更高。
而对于原本就须要在Map端进行排序的Shuffle操做来讲,如ReduceByKey等,使用HashShuffleManager虽然在写数据时不排序,但在其它的步骤中仍然须要排序,而SortShuffleManager则能够将写数据和排序两个工做合并在一块儿执行,所以即便不考虑HashShuffleManager的内存使用问题,SortShuffleManager依旧可能更快。
spark.shuffle.sort.bypassMergeThreshold
这个参数仅适用于SortShuffleManager,如前所述,SortShuffleManager在处理不须要排序的Shuffle操做时,因为排序带来性能的降低。这个参数决定了在这种状况下,当Reduce分区的数量小于多少的时候,在SortShuffleManager内部不使用Merge Sort的方式处理数据,而是与Hash Shuffle相似,直接将分区文件写入单独的文件,不一样的是,在最后一步仍是会将这些文件合并成一个单独的文件。这样经过去除Sort步骤来加快处理速度,代价是须要并发打开多个文件,因此内存消耗量增长,本质上是相对HashShuffleMananger一个折衷方案。 这个参数的默认值是200个分区,若是内存GC问题严重,能够下降这个值。
spark.shuffle.consolidateFiles
这个配置参数仅适用于HashShuffleMananger的实现,一样是为了解决生成过多文件的问题,采用的方式是在不一样批次运行的Map任务之间重用Shuffle输出文件,也就是说合并的是不一样批次的Map任务的输出数据,可是每一个Map任务所须要的文件仍是取决于Reduce分区的数量,所以,它并不减小同时打开的输出文件的数量,所以对内存使用量的减小并无帮助。只是HashShuffleManager里的一个折中的解决方案。
须要注意的是,这部分的代码实现尽管原理上说很简单,可是涉及到底层具体的文件系统的实现和限制等因素,例如在并发访问等方面,须要处理的细节不少,所以一直存在着这样那样的bug或者问题,致使在例如EXT3上使用时,特定状况下性能反而可能降低,所以从Spark 0.8的代码开始,一直到Spark 1.1的代码为止也尚未被标志为Stable,不是默认采用的方式。此外由于并不减小同时打开的输出文件的数量,所以对性能具体能带来多大的改善也取决于具体的文件数量的状况。因此即便你面临着Shuffle文件数量巨大的问题,这个配置参数是否使用,在什么版本中可使用,也最好仍是实际测试之后再决定。
spark.shuffle.spill
shuffle的过程当中,若是涉及到排序,聚合等操做,势必会须要在内存中维护一些数据结构,进而占用额外的内存。若是内存不够用怎么办,那只有两条路能够走,一就是out of memory 出错了,二就是将部分数据临时写到外部存储设备中去,最后再合并到最终的Shuffle输出文件中去。
这里spark.shuffle.spill 决定是否Spill到外部存储设备(默认打开),若是你的内存足够使用,或者数据集足够小,固然也就不须要Spill,毕竟Spill带来了额外的磁盘操做。
spark.shuffle.memoryFraction/ spark.shuffle.safetyFraction
在启用Spill的状况下,spark.shuffle.memoryFraction(1.1后默认为0.2)决定了当Shuffle过程当中使用的内存达到总内存多少比例的时候开始Spill。
经过spark.shuffle.memoryFraction能够调整Spill的触发条件,即Shuffle占用内存的大小,进而调整Spill的频率和GC的行为。总的来讲,若是Spill太过频繁,能够适当增长spark.shuffle.memoryFraction的大小,增长用于Shuffle的内存,减小Spill的次数。固然这样一来为了不内存溢出,对应的可能须要减小RDD cache占用的内存,即减少spark.storage.memoryFraction的值,这样RDD cache的容量减小,有可能带来性能影响,所以须要综合考虑。
因为Shuffle数据的大小是估算出来的,一来为了下降开销,并非每增长一个数据项都完整的估算一次,二来估算也会有偏差,因此实际暂用的内存可能比估算值要大,这里spark.shuffle.safetyFraction(默认为0.8)用来做为一个保险系数,下降实际Shuffle使用的内存阀值,增长必定的缓冲,下降实际内存占用超过用户配置值的几率。
spark.shuffle.spill.compress/ spark.shuffle.compress
这两个配置参数都是用来设置Shuffle过程当中是否使用压缩算法对Shuffle数据进行压缩,前者针对Spill的中间数据,后者针对最终的shuffle输出文件,默认都是True
理论上说,spark.shuffle.compress设置为True一般都是合理的,由于若是使用千兆如下的网卡,网络带宽每每最容易成为瓶颈。此外,目前的Spark任务调度实现中,以Shuffle划分Stage,下一个Stage的任务是要等待上一个Stage的任务所有完成之后才能开始执行,因此shuffle数据的传输和CPU计算任务之间一般不会重叠,这样Shuffle数据传输量的大小和所需的时间就直接影响到了整个任务的完成速度。可是压缩也是要消耗大量的CPU资源的,因此打开压缩选项会增长Map任务的执行时间,所以若是在CPU负载的影响远大于磁盘和网络带宽的影响的场合下,也可能将spark.shuffle.compress 设置为False才是最佳的方案
对于spark.shuffle.spill.compress而言,状况相似,可是spill数据不会被发送到网络中,仅仅是临时写入本地磁盘,并且在一个任务中同时须要执行压缩和解压缩两个步骤,因此对CPU负载的影响会更大一些,而磁盘带宽(若是标配12HDD的话)可能每每不会成为Spark应用的主要问题,因此这个参数相对而言,或许更有机会须要设置为False。
总之,Shuffle过程当中数据是否应该压缩,取决于CPU/DISK/NETWORK的实际能力和负载,应该综合考虑。
转自:http://blog.csdn.net/colorant/article/details/38680581