引子
开发大数据的程序时间一长,就会发现和以前开发其它程序的方式有很大的不同。
除了要将更多的关注点放在数据上之外,一个很重要的事情是,你开发的程序在测试库运行的好好的,
到线上数据量大之后,程序执行就可能会出各种问题,有时狂加资源程序也不能很好的执行。
这其中就会涉及到一些spark程序调优方面的内容,网上一翻,会出现一大堆这种方面的东西,
有些还是很有用的,能解决工作中的实际问题。这里就将工作中碰到的调优问题和解决方案做分享
执行参数调优
spark的每一个参数格式及对参数的解释和默认值都能在官网上找到,这里列举在工作中用的较多的参数
参数
| 参数解释 | 使用场景 |
---|---|---|
spark.executor.memory spark.executor.instances spark.executor.cores | 三个参数决定了spark总的内存,同一时间执行的最大任务数量,以及每个task运行内存的上限 executor memory * executor instance 为executor端总的内存数(暂不考虑堆外内存和pyspark worker memory情况) executor core * executor instance 为程序计算的最大并发数(即同时执行的task数量上限) 每个task使用的内存区间会在 executor_memory / executor_core 到 executor_memory / (2 * executor_core)内摆动 在实际进行计算时,有没有申请到这么多资源一定要看内存监控(像 yarn top),一个很重要的点,如果总的core申请了100,但是给到你的core只有20,那么即使在监控上你能看到给了你满额的内存,其实有80%的内存都没有利用起来,因为你同时能利用的core只有那么多。内存溢出了可以看看是不是这个原因引起的 | 计算程序上线,对资源申请的时候,需要根据数据量和集群情况多关注这几个参数 |
spark.default.parallelism spark.sql.shuffle.partitions | default.parallelism 是 rdd做转换操作像 join, reduceByKey,parallelize 时的默认分区数,不设置的话默认是200。该参数对rdd有效,对dataframe无效 sql.shuffle.partitions 在 spark3.0 以前官方文档上看不到这个参数,但是spark2.3+设置了还是有效果(再前面的版本没有验证过),用于设置join和aggregation做shuffling时的默认分区数。 这两个参数的区别有些微妙,如果不知道是哪个参数要生效建议将两个都设置。二者区别描述的可参看:讨论 这两个参数决定了spark运行时默认分区数量,数据量大的,分区数多设置些,每个task运行时占用的内存会小些 | 计算程序对hdfs上大量小文件的处理很影响程序性能,对超大文件处理可能导致内存问题。默认并发数决定了默认分区数,数据落盘hdfs时,为避免大量小文件和超大文件需要注意设置默认分区。 |
spark.driver.memory spark.yarn.am.memory | driver.memory是cluster模式下driver运行内存,yarn.am.memory是yarn client模式下driver运行内存。运行内存默认1G,需注意,这个参数不能在应用程序启动后在sparkconf里面设置,需要在命令行设置 | driver端需要做大量耗内存操作如读取本地大文件创建dataframe;或是需要使用像 collect,take这种算子时,需要注意调整driver memory和 spark.driver.maxResultSize的值 |
spark.shuffle.file.buffer spark.shuffle.io.maxRetries spark.reducer.maxSizeInFlight spark.shuffle.sort.bypassMergeThreshold spark.shuffle.memoryFraction | spark.shuffle.file.buffer指定做shuffle write 时,内存缓冲区的大小,默认32k,在内存比较充足的情况下,可以适当调大这个参数(如64k),减少溢写磁盘的次数,性能会有所提升;spark.shuffle.io.maxRetries指定shuffle read 时,拉取数据失败重试次数,默认3次,在计算数据量很大, JVM full gc 较多的情况下,可以将该参数调整大些,以提高程序稳定性;spark.reducer.maxSizeInFlight指定shuffle read 时,缓冲区的大小,默认48M,在内存充足的情况下,可适当调大(比如96M),可减少网络传输次数,性能会有所提升;spark.shuffle.memoryFraction指定executor内存中,分配给shuffle read task 进行聚合操作的内存比例,默认 20%,如果内存充足且很少使用持久化操作,可以适当调高比例(如 30%),可避免因内存不足导致聚合中频繁读写磁盘。参考文档 | shuffle过程的参数调优对性能的提升***非常有限***,如果其它方面的调优的做的差不多了可以考虑进行这些方法的调优 |
spark.network.timeout spark.shuffle.io.connectionTimeout spark.rpc.askTimeout | 超时时间设置,如果不手动设置,后面两个的超时时间默认使用network.timeout的超时时间。 | 默认的超时时间为120s,通常情况下这个是够用的。如果程序中出现因这个时间设置而超时的情况,并在报错中提示延长这个时间,首先可以根据报错的地方看看代码是否有问题,再想想资源配置是否合理,如果需要调整就先做这两项调整;如果这两项看不出问题,可酌情调高timeout的值,等调的比较高了还是继续报超时的话,还是得重点看程序方面的问题,是不是需要做优化 |
spark.sql.autoBroadcastJoinThreshold spark.sql.broadcastTimeout | autoBroadcastJoinThreshold设置join时,如果要进行broadcast join,要广播的表的大小阈值,超过该值就不会进行自动广播,当然就没有 broadcast join 了,这个配置在官网配置的旧版本的spark中是看不到的,但是设置了会生效。另外,别以为有这个设置且参与join的表的data size 满足阈值要求就万事大吉了。仔细看官方文档说明会发现,只有在spark能拿到表的data size时这个设置才生效。总结下来,其实我们创建的临时表是没有这个待遇的。临时表要想使用就得手动进行广播。 broadcastTimeout 用来设置广播的超时时间,默认是300秒,如果广播的变量比较大并且节点又很多,需要去将这个人值调大 | 为提升速度,尽量减少shuffle,且适当增大广播变量大小后能减少很多shuffle,可考虑调整 autoBroadcastJoinThreshold 的值。 中间临时表数据量较小并且又用的很多,对速度有较大的影响,可以考虑手动广播数据,并根据需要调整广播超时时间 |
spark.locality.wait spark.locality.wait.node spark.locality.wait.process spark.locality.wait.rack | 数据本地化设置,spark计算的数据和计算任务(task)是分开的,要想计算数据需要将数据和计算任务放到一起。通常情况下移动数据会比较大,要将两者放在一起,移动数据的代价会比移动计算任务的代价要大。为了提升效率,spark采取了数据本地化策略,让数据移动的距离尽量小。 spark实现时是采用延迟调度策略来让数据的本地化尽可能优化,数据本地化分五个级别:PROCESS_LOCAL, NODE_LOCAL,NO_PREF,RACK_LOCAL,ANY。如果数据在node1的executor1中,那么执行这个数据的task1会发到executor1上,这时如果executor1正在执行其它任务,则task1根据设置的本地化等待延迟时间进行等待。如果超过了等待时间还是等不到计算资源。task1的本地化策略会降级,比如降为NODE_LOCAL,这是task1会发送到node1的另一个executor,比如executor2上。如果这时没有拿到资源,重复上面的步骤后再降级。如果在executor2上拿到计算资源,则将数据移动过来进行计算,这时数据移动的距离会比PROCESS_LOCAL远些,但是相对其他级别也很近 | 如果Task的输入数据比较大,那么耗费在数据读取上的时间会比较长,一个好的数据本地性能够节省很长时间,所以这种情况下最好还是将延迟调度的降级等待时间调长一些。而对于输入数据比较小的,即使数据本地性不好也只是多花一点点时间,那么便不必在延迟调度上耗费太长时间。总结一下就是如果数据本地性对任务的执行时间影响较大的话就稍稍调高延迟调度的降级等待时间。参考blog |
spark.hadoop.dfs.blocksize spark.hadoop.dfs.replication | spark对hadoop在配置配置在官网上也是查不到的,不过官方文档有说如何去设置hadoop的相关参数,也可以参考stackoverflow上的示例。hadoop.dfs.blocksize用于设置数据块的大小,默认为64M或128M或256M,hadoop.dfs.replication用于设置数据块的副本数,默认为3 | 在需要提高计算速度,落盘hdfs又很多的情况下,可考虑适当降低副本个数。为了一个block数据多些可以适当调高blocksize的数据量 |
spark.serializer | spark需要通过网络传输数据,或是将数据溢写到磁盘,做这些操作时,需要对数据进项序列化操作,在数据量很大的时候,序列化操作会占用很多的时间。spark默认的序列化类是org.apache.spark.serializer.JavaSerializer,该类提供的序列化速度会很慢,所以官方文档推荐,想要计算速度快些,可以将此项修改为org.apache.spark.serializer.KryoSerializer,并做相应的配置 | 需要计算速度有所提高,从参数优化的角度可以考虑将序列化修改为org.apache.spark.serializer.KryoSerializer |
spark.shuffle.service.enabled spark.dynamicAllocation.enabled spark.dynamicAllocation.executorIdleTimeout spark.dynamicAllocation.minExecutors spark.dynamicAllocation.maxExecutors | 设置程序为动态分配资源 conf.set(“spark.shuffle.service.enabled”, true) conf.set(“spark.dynamicAllocation.enabled”, true) conf.set(“spark.dynamicAllocation.executorIdleTimeout”, 5) conf.set(“spark.dynamicAllocation.minExecutors”, 5) conf.set(“spark.dynamicAllocation.maxExecutors”, 50) | 在整个sparkcontext运行的生命周期中,申请到的资源都是不会释放的。生命周期中的每个job需要的资源可能会有很大的变化,在能较好的评估各个阶段运行资源的情况下,可以修改为动态申请资源。但一定要注意***设置资源的上限*** |