目的
关于spark程序优化总结,包括参数调优、RDD优化、算子优化等。对于处理大数据量的spark程序而言,如果做好调优,将会有比较明显的效果。从个人而言,是锻炼提升自己的机会;从项目而言,是用最小的资源做最优的事情。下面是我在工作过程中遇到的调优记录,可能不够全面,不过学会了这些,一些简单的调优还是没什么问题的。即使是菜鸟,一步步努力,不骄不躁,终会达到一个比较好的高度!与君共勉之!
环境
软件 | 版本 |
---|---|
spark | 1.6 |
调优步骤
参数优化
属性名 | 默认值 | 描述 |
---|---|---|
spark.executor.instances | 2 | 静态分配的executor数。 如果同时使用spark.dynamicAllocation.enabled和spark.executor.instances时,则动态分配将会被关闭,将会使用spark.executor.instances的值 |
spark.executor.memory | 1g | 每个executor进程使用的内存大小,格式与具有大小单位后缀(k,m,g,t)的JVM内存字符串相同(1024m,2g) |
spark.executor.cores | 1 | 每个executor使用的核心数 |
spark.executor.memoryOverhead | executorMemory * 0.10, 最小为384 | 除非另有说明,否则每个executor要分配的堆外内存量(MiB) 这是一个内存,可以解决诸如VM开销,以及其他本机开销等问题。这往往会随着执行程序的大小而增加(通常为6-10%)。 YARN和Kubernetes目前支持此选项。 |
spark.sql.autoBroadcastJoinThreshold | 10485760 (10 MB) | 配置该表在执行联接时可以被广播到所有工作程序节点的最大大小(以字节为单位)。如果设置为-1,则广播将会失效。请注意,当前统计信息仅适用于已运行ANALYZE TABLE COMPUTE STATISTICS noscan命令的Hive Metastore表 |
spark.shuffle.file.buffer | 32k | 每个随机播放文件输出流的内存缓冲区大小。这些缓冲区减少了在创建中间混排文件时进行的磁盘查找和系统调用的数量。如果内存比较充足,可以调大该值,减少shuffle write过程中溢写磁盘文件的次数,减少磁盘IO次数,进而提升性能 |
spark.reducer.maxSizeInFlight | 48m | 从每个reduce任务中同时获取的映射输出的最大大小。由于每个输出都需要我们创建一个缓冲区来接收它,因此这代表每个reduce任务固定的内存开销,因此,请使其保持较小,除非有大量的内存。同理,如果内存较大,可以考虑调大该值,减少网络消耗 |
spark.shuffle.io.maxRetries | 3 | 如果将其设置为非零值,则会自动重试由于与IO相关的异常而失败的提取。这种重试逻辑有助于在长时间的GC暂停或瞬态网络连接问题时稳定大数据量的shuffle。对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数,比如30次。 |
RDD优化
rdd复用
如果使用已有rdd进行转换就可以得到想要的数据,就复用该rdd。
rdd持久化
当同个rdd被重复调用action算子的时候,每一次都会重新计算该算子的父rdd。对同一个RDD的重复计算是对资源的极大浪费,所以有必要进行资源的持久化。当内存无法将RDD的数据完整的进行存放的时候,可以考虑使用序列化的方式减小数据体积,将数据完整存储在内存中。
广播大变量
如果task在运行过程中,有调用外部变量的话,那每一个task在运行过程中,都会调用一份变量存储在本地,造成内存的极大消耗。那么内存被消耗了,就会导致本来可以被持久化的数据没办法持久化,只能存放到磁盘,导致磁盘IO变大,严重消耗资源。另一方面,内存逐渐被占满,当task要创建新变量的时候,内存不足,就会触发GC。而GC会暂停工作线程,进而导致spark进程会暂停工作一行,从而严重影响spark性能。如果广播了大变量,则每一个executor都只会存储一份这样的变量,每个task都会从本地的BlockManager获取。
算子优化
mapPartitions
mapPartitions 和 map 的不同是:mapPartitions 会直接抽取一个partition的数据进行处理,而map是一个个的进行处理。如果数据量不大、内存充足、网络等待比较大,可以考虑用这个参数进行处理。
foreachPartition
这个和上面的 mapPartitions 差不多,都是对一个partition进行处理。这个操作适合和数据库操作结合在一起。假如需要连接数据库,如果用 foreach 会导致严重的数据库连接数,消耗数据库资源。而使用 foreachPartition 的话,则只会一个 partition 启动一个数据库连接。另外,如果数据量过大,会导致OOM。所以如果发生这种情况,可以先进行重新分区。
repartition
使用spark SQL的时候,会自动根据数据进行动态分区。如果处理过程中,算子的处理逻辑较为复杂,并且数据量较大,而spark SQL设置的partition比较少,就会导致一个问题:不多的task会处理该数据量大的partition,而且处理逻辑还比较复杂,进而导致处理速度缓慢。所以可以在使用spark SQL,可以根据情况,适当地增大或者缩小该分区数量,从而调整处理并行度,进而加快处理速度。
存储文件优化
如果是hive、spark SQL结合使用的话,根据具体的情况使用对应的存储文件格式。在我的实际项目中,有两种压缩方式,一种是对普通文本文件的lzo压缩,一种是对parquet的gzip压缩。 如果是spark程序运行保存表,一般指定表存储方式为parquet文件,然后在保存过程中指定gzip压缩。如果是hive脚本跑数,则指定为普通文本文件,并使用lzo进行压缩。
下图是我做的测试数据,表是相同的表,只是命名不同。字段数据量大概为20个左右。第一个表是没有做压缩的数据大小,为69.8G;第二个表是做lzo压缩方式,为24.7G;第三个表是使用了parquet的gzip压缩方式,为12.8GB。一般lzo压缩,可以压缩到原来的三分之一,而parquet的gzip压缩,根据数据的不同而呈现不同的压缩比,我们的测试结果是可以压缩到原来的百分之十八。另一方面, lzo压缩,是对整个文本进行压缩;而parquet文件是列式存储,在实际应用中,我们一般是只取表的几列来进行运算,使用parquet存储可以增加读取效率。从下图可以知道,parquet文件压缩的文件,占用的计算资源更少,结果输出更快。
表 | 压缩方式 | 存储大小 | 运行时间 | 运算资源(mappers) | 运算资源(reducers) |
---|---|---|---|---|---|
test_txt | 无压缩 | 69.8G | 429.12s | 277 | 1099 |
test_lzo | lzo压缩 | 24.7G | 450.37s | 101 | 396 |
test_d | parquet文件gzip格式压缩 | 12.8GB | 364.04s | 97 | 206 |
参考链接
Spark SQL, DataFrames and Datasets Guide
随缘求赞
如果我的文章对大家产生了帮忙,可以在文章底部点个赞或者收藏;
如果有好的讨论,可以留言;
如果想继续查看我以后的文章,可以点击关注
可以扫描以下二维码,关注我的公众号:枫夜之求索阁,查看我最新的分享!