spark-sql调优
性能优化参数
在spark中,Spark SQL性能调优只要是通过下面的一些选项进行优化的:
1 spark.sql.codegen 默认值为false,当它设置为true时,Spark SQL会把每条查询的语句在运行时编译为java的二进制代码。这有什么作用呢?它可以提高大型查询的性能,但是如果进行小规模的查询的时候反而会变慢,就是说直接用查询反而比将它编译成为java的二进制代码快。所以在优化这个选项的时候要视情况而定。
2 spark.sql.inMemoryColumnStorage.compressed 默认值为false 它的作用是自动对内存中的列式存储进行压缩
3 spark.sql.inMemoryColumnStorage.batchSize 默认值为1000 这个参数代表的是列式缓存时的每个批处理的大小。如果将这个值调大可能会导致内存不够的异常,所以在设置这个的参数的时候得注意你的内存大小
4
spark.sql.parquet.compressed.codec 默认值为snappy 这个参数代表使用哪种压缩编码器。可选的选项包括uncompressed/snappy/gzip/lzo
uncompressed这个顾名思义就是不用压缩的意思
下面是总结的几种压缩选项的特点
格式 | 可分割 | 平均压缩速度 | 文本文件压缩效率 | Hadoop压缩编解码器 | 纯java实现 | 原生 | 备注 |
snappy | 否 | 非常快 | 低 | org.apache.hadoop.io. compress.SnappyCodec |
否 | 是 |
Snappy有纯java
的移植版,
但是在Spark/
Hadoop中
不能用
|
gzip | 否 | 快 | 高 | org.apache.hadoop.io. compress.GzipCodec |
是 | 是 | |
lzo | 否 | 非常快 | 中等 | org.apache.hadoop.io. compress.LzoCodec |
是 | 是 | 需要在每个节点上 安装LZO |
当然在设置上面这些参数的时候需要给予特别的考量。第一spark.sql.codegen,这个选项可以让Spark SQL把每条查询语句在运行前编译为java二进制代码,由于生成了专门运行指定查询的代码,codegen可以让大型查询或者频繁重复的查询明显变快,然而在运行特别快(1-2秒)的即时查询语句时,codegen就可能增加额外的开销(将查询语句编译为java二进制文件)。
codegen还是一个实验性的功能,但是在大型的或者重复运行的查询中使用codegen。
调优时可能还需要考虑第二个选项是spark.sql.inMemoryColumnarStorage.batchSize,在缓存SchemaRDD(Row RDD)时,Spark SQL会安照这个选项设定的大小(默认为1000)把记录分组,然后分批次压缩。
太小的批处理会导致压缩比过低,而太大的话,比如当每个批处理的数据超过内存所能容纳的大小时,也有可能引发问题。
如果你表中的记录比价大(包含数百个字段或者包含像网页这样非常大的字符串字段),就可能需要调低批处理的大小来避免内存不够(OOM)的错误。如果不是在这样的场景下,默认的批处理 的大小是比较合适的,因为压缩超过1000条压缩记录时也基本无法获得更高的压缩比了。
以下是在学习和使用spark过程中遇到的一些问题,记录下来。
1、首先来说说spark任务运行完后查错最常用的一个命令,那就是把任务运行日志down下来。 程序存在错误,将日志down下来查看具体原因!down日志命令:yarn logs -applicationId app_id
2、Spark性能优化的9大问题及其解决方案
Spark程序优化所需要关注的几个关键点——最主要的是数据序列化和内存优化
问题1:reduce task数目不合适
解决方法:需根据实际情况调节默认配置,调整方式是修改参数spark.default.parallelism。通常,reduce数目设置为core数目的2到3倍。数量太大,造成很多小任务,增加启动任务的开销;数目太少,任务运行缓慢。
问题2:shuffle磁盘IO时间长
解决方法:设置spark.local.dir为多个磁盘,并设置磁盘为IO速度快的磁盘,通过增加IO来优化shuffle性能;
问题3:map|reduce数量大,造成shuffle小文件数目多
解决方法:默认情况下shuffle文件数目为map tasks * reduce tasks. 通过设置spark.shuffle.consolidateFiles为true,来合并shuffle中间文件,此时文件数为reduce tasks数目;
问题4:序列化时间长、结果大
解决方法:Spark默认使.用JDK.自带的ObjectOutputStream,这种方式产生的结果大、CPU处理时间长,可以通过设置spark.serializer为org.apache.spark.serializer.KryoSerializer。另外如果结果已经很大,可以使用广播变量;
问题5:单条记录消耗大
解决方法:使用mapPartition替换map,mapPartition是对每个Partition进行计算,而map是对partition中的每条记录进行计算;
问题6:collect输出大量结果时速度慢
解决方式:collect源码中是把所有的结果以一个Array的方式放在内存中,可以直接输出到分布式?文件系统,然后查看文件系统中的内容;
问题7:任务执行速度倾斜
解决方式:如果是数据倾斜,一般是partition key取的不好,可以考虑其它的并行处理方式 ,并在中间加上aggregation操作;如果是Worker倾斜,例如在某些worker上的executor执行缓慢,可以通过设置spark.speculation=true 把那些持续慢的节点去掉;
问题8:通过多步骤的RDD操作后有很多空任务或者小任务产生
解决方式:使用coalesce或repartition去减少RDD中partition数量;
问题9:Spark Streaming吞吐量不高
解决方式:可以设置spark.streaming.concurrentJobs
3、intellij idea直接编译spark源码及问题解决:
http://blog.csdn.net/tanglizhe1105/article/details/50530104
http://stackoverflow.com/questions/18920334/output-path-is-shared-between-the-same-module-error
Spark编译:clean package -Dmaven.test.skip=true
参数:-Xmx2g -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=512m
4、import Spark source code into intellj, build Error:
not found: type SparkFlumeProtocol and EventBatch
http://stackoverflow.com/questions/33311794/import-spark-source-code-into-intellj-build-error-not-found-type-sparkflumepr
spark_complie_config.png
5、org.apache.spark.SparkException: Exception thrown in awaitResult
set "spark.sql.broadcastTimeout" to increase the timeout
6、Apache Zeppelin编译安装:
Apache Zeppelin installation grunt build error:
解决方案:进入web模块npm install;
http://stackoverflow.com/questions/33352309/apache-zeppelin-installation-grunt-build-error?rq=1
7、Spark源码编译遇到的问题解决:http://www.tuicool.com/articles/NBVvai
内存不够,这个错误是因为编译的时候内存不够导致的,可以在编译的时候加大内存。
[ERROR] PermGen space -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors,re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions,
please read the following articles:
[ERROR] [Help 1]http://cwiki.apache.org/confluence/display/MAVEN/OutOfMemoryError
export MAVEN_OPTS="-Xmx2g -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=512m"
8、Exception in thread "main" java.lang.UnsatisfiedLinkError: no jnind4j in java.library.path
解决方案:I’m using a 64-Bit Java on Windows and still get the no jnind4j in java.library.path error It may be that you have incompatible DLLs on your PATH. In order to tell DL4J to ignore those you have to add the following as a VM parameter (Run -> Edit Configurations -> VM Options in IntelliJ): -Djava.library.path=""
9、spark2.0本地运行源码报错解决办法:
修改对应pom中的依赖jar包,将scope级别由provided改为compile
运行类之前,去掉make选项;在运行vm设置中增加-Dspark.master=local
Win7下运行spark example代码报错:
java.lang.IllegalArgumentException: java.net.URISyntaxException: Relative path in absolute URI: file:D:/SourceCode/spark-2.0.0/spark-warehouse修改SQLConf类中WAREHOUSE_PATH变量,将file:前缀改为file:/或file:///
createWithDefault("file:/${system:user.dir}/spark-warehouse")
local模式运行:-Dspark.master=local
10、解决Task not serializable Exception错误
方法1:将RDD中的所有数据通过JDBC连接写入数据库,若使用map函数,可能要为每个元素都创建connection,这样开销很大,如果使用mapPartitions,那么只需要针对每个分区建立connection;mapPartitions处理后返回的是Iterator。
方法2:对未序列化的对象加@transisent引用,在进行网络通信时不对对象中的属性进行序列化
11、这个函数在func("11")调用时候正常,但是在执行func(11)或func(1.1)时候就会报error: type mismatch的错误. 这个问题很好解决
针对特定的参数类型, 重载多个func函数,这个不难, 传统JAVA中的思路, 但是需要定义多个函数
使用超类型, 比如使用AnyVal,Any;这样的话比较麻烦,需要在函数中针对特定的逻辑做类型转化,从而进一步处理上面两个方法使用的是传统JAVA思路,虽然都可以解决该问题,但是缺点是不够简洁;在充满了语法糖的Scala中,针对类型转换提供了特有的implicit隐式转化的功能;
12、org.apache.spark.shuffle.MetadataFetchFailedException: Missing an output location for shuffle
解决方案:这种问题一般发生在有大量shuffle操作的时候,task不断的failed,然后又重执行,一直循环下去,直到application失败。一般遇到这种问题提高executor内存即可,同时增加每个executor的cpu,这样不会减少task并行度。
13、Spark ML PipeLine GBT/RF预测时报错,java.util.NoSuchElementException: key not found: 8.0
错误原因:由于GBT/RF模型输入setFeaturesCol,setLabelCol参数列名不一致导致。
解决方案:只保存训练算法模型,不保存PipeLineModel
14、linux删除乱码文件,step1. ls -la; step2. find . -inum inode num -exec rm {} -rf \;
15、Caused by: java.lang.RuntimeException: Failed to commit task Caused by: org.apache.spark.executor.CommitDeniedException: attempt_201603251514_0218_m_000245_0: Not committed because the driver did not authorize commit
如果你比较了解spark中的stage是如何划分的,这个问题就比较简单了。一个Stage中包含的task过大,一般由于你的transform过程太长,因此driver给executor分发的task就会变的很大。所以解决这个问题我们可以通过拆分stage解决。也就是在执行过程中调用cache.count缓存一些中间数据从而切断过长的stage。
代码实例
import java.util.List;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.api.java.JavaSQLContext;
import org.apache.spark.sql.api.java.Row;
import org.apache.spark.sql.hive.api.java.JavaHiveContext;
public class PerformanceTuneDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("simpledemo").setMaster("local");
conf.set("spark.sql.codegen", "false");
conf.set("spark.sql.inMemoryColumnarStorage.compressed", "false");
conf.set("spark.sql.inMemoryColumnarStorage.batchSize", "1000");
conf.set("spark.sql.parquet.compression.codec", "snappy");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaSQLContext sqlCtx = new JavaSQLContext(sc);
JavaHiveContext hiveCtx = new JavaHiveContext(sc);
List<Row> result = hiveCtx.sql("SELECT foo,bar,name from pokes2 limit 10").collect();
for (Row row : result) {
System.out.println(row.getString(0) + "," + row.getString(1) + "," + row.getString(2));
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
Beenline命令行设置参数
beeline> set spark.sql.codegen=true;
SET spark.sql.codegen=true
spark.sql.codegen=true
Time taken: 1.196 seconds
- 1
- 2
- 3
- 4
参数说明
spark.sql.codegen Spark SQL在每次执行次,先把SQL查询编译JAVA字节码。针对执行时间长的SQL查询或频繁执行的SQL查询,此配置能加快查询速度,因为它产生特殊的字节码去执行。但是针对很短(1 - 2秒)的临时查询,这可能增加开销,因为它必须先编译每一个查询。
spark.sql.inMemoryColumnarStorage.batchSize:
When caching SchemaRDDs, Spark SQL groups together the records in the RDD in batches of the size given by this option (default: 1000), and compresses each batch. Very small batch sizes lead to low compression, but on the other hand very large sizes can also be problematic, as each batch might be too large to build up in memory.
spark是一个快速的内存计算框架;同时是一个并行运算的框架。在计算性能调优的时候,除了要考虑广为人知的木桶原理外,还要考虑平行运算的Amdahl定理。
木桶原理又称短板理论,其核心思想是:一只木桶盛水的多少,并不取决于桶壁上最高的那块木块,而是取决于桶壁上最短的那块。将这个理论应用到系统性能优化上,系统的最终性能取决于系统中性能表现最差的组件。例如,即使系统拥有充足的内存资源和CPU资源,但是如果磁盘I/O性能低下,那么系统的总体性能是取决于当前最慢的磁盘I/O速度,而不是当前最优越的CPU或者内存。在这种情况下,如果需要进一步提升系统性能,优化内存或者CPU资源是毫无用处的。只有提高磁盘I/O性能才能对系统的整体性能进行优化。
Amdahl定理,一个计算机科学界的经验法则,因吉恩·阿姆达尔而得名。它代表了处理器平行运算之后效率提升的能力。并行计算中的加速比是用并行前的执行速度和并行后的执行速度之比来表示的,它表示了在并行化之后的效率提升情况。阿姆达尔定律是固定负载(计算总量不变时)时的量化标准。可用公式:\frac{W_s + W_p}{W_s + \frac{W_p}{p}}来表示。式中W_s, W_p分别表示问题规模的串行分量(问题中不能并行化的那一部分)和并行分量,p表示处理器数量。当p\to \infty时,上式的极限是\frac{W}{W_s},其中,{W}={W_s}+{W_p}。这意味着无论我们如何增大处理器数目,加速比是无法高于这个数的。
SparkSQL作为Spark的一个组件,在调优的时候,也要充分考虑到上面的两个原理,既要考虑如何充分的利用硬件资源,又要考虑如何利用好分布式系统的并行计算。由于测试环境条件有限,本篇不能做出更详尽的实验数据来说明,只能在理论上加以说明。
- 1
- 2
1:并行性
SparkSQL在集群中运行,将一个查询任务分解成大量的Task分配给集群中的各个节点来运行。通常情况下,Task的数量是大于集群的并行度。比如前面第六章和第七章查询数据时,shuffle的时候使用了缺省的spark.sql.shuffle.partitions,即200个partition,也就是200个Task:
而实验的集群环境却只能并行3个Task,也就是说同时只能有3个Task保持Running:
这时大家就应该明白了,要跑完这200个Task就要跑200/3=67批次。如何减少运行的批次呢?那就要尽量提高查询任务的并行度。查询任务的并行度由两方面决定:集群的处理能力和集群的有效处理能力。
- 对于Spark Standalone集群来说,集群的处理能力是由conf/spark-env中的SPARK_WORKER_INSTANCES参数、SPARK_WORKER_CORES参数决定的;而SPARK_WORKER_INSTANCES*SPARK_WORKER_CORES不能超过物理机器的实际CPU core;
- 集群的有效处理能力是指集群中空闲的集群资源,一般是指使用spark-submit或spark-shell时指定的–total-executor-cores,一般情况下,我们不需要指定,这时候,Spark Standalone集群会将所有空闲的core分配给查询,并且在Task轮询运行过程中,Standalone集群会将其他spark应用程序运行完后空闲出来的core也分配给正在运行中的查询。
2,高效的数据格式
高效的数据格式,一方面是加快了数据的读入速度,另一方面可以减少内存的消耗。高效的数据格式包括多个方面:
- 1
- 2
2.1 数据本地性
分布式计算系统的精粹在于移动计算而非移动数据,但是在实际的计算过程中,总存在着移动数据的情况,除非是在集群的所有节点上都保存数据的副本。移动数据,将数据从一个节点移动到另一个节点进行计算,不但消耗了网络IO,也消耗了磁盘IO,降低了整个计算的效率。为了提高数据的本地性,除了优化算法(也就是修改spark内存,难度有点高),就是合理设置数据的副本。设置数据的副本,这需要通过配置参数并长期观察运行状态才能获取的一个经验值。
下面是spark webUI监控Stage的一个图:
- -PROCESS_LOCAL是指读取缓存在本地节点的数据
- NODE_LOCAL是指读取本地节点硬盘数据
- ANY是指读取非本地节点数据
- 通常读取数据PROCESS_LOCAL>NODE_LOCAL>ANY,尽量使数据以PROCESS_LOCAL或NODE_LOCAL方式读取。其中PROCESS_LOCAL还和cache有关。
2.2 合适的数据类型
对于要查询的数据,定义合适的数据类型也是非常有必要。对于一个tinyint可以使用的数据列,不需要为了方便定义成int类型,一个tinyint的数据占用了1个byte,而int占用了4个byte。也就是说,一旦将这数据进行缓存的话,内存的消耗将增加数倍。在SparkSQL里,定义合适的数据类型可以节省有限的内存资源。
2.3 合适的数据列
对于要查询的数据,在写SQL语句的时候,尽量写出要查询的列名,如Select a,b from tbl,而不是使用Select * from tbl;这样不但可以减少磁盘IO,也减少缓存时消耗的内存。
2.4 更优的数据存储格式
在查询的时候,最终还是要读取存储在文件系统中的文件。采用更优的数据存储格式,将有利于数据的读取速度。查看sparkSQL的stage,可以发现,很多时候,数据读取消耗占有很大的比重。对于sqlContext来说,支持 textFiile、SequenceFile、ParquetFile、jsonFile;对于hiveContext来说,支持AvroFile、ORCFile、Parquet File,以及各种压缩。根据自己的业务需求,测试并选择合适的数据存储格式将有利于提高sparkSQL的查询效率。
3:内存的使用
spark应用程序最纠结的地方就是内存的使用了,也是最能体现“细节是魔鬼”的地方。Spark的内存配置项有不少,其中比较重要的几个是:
- SPARK_WORKER_MEMORY,在conf/spark-env.sh中配置SPARK_WORKER_MEMORY 和SPARK_WORKER_INSTANCES,可以充分的利用节点的内存资源,SPARK_WORKER_INSTANCES*SPARK_WORKER_MEMORY不要超过节点本身具备的内存容量;
- executor-memory,在spark-shell或spark-submit提交spark应用程序时申请使用的内存数量;不要超过节点的SPARK_WORKER_MEMORY;
- spark.storage.memoryFraction spark应用程序在所申请的内存资源中可用于cache的比例
-
spark.shuffle.memoryFraction spark应用程序在所申请的内存资源中可用于shuffle的比例
在实际使用上,对于后两个参数,可以根据常用查询的内存消耗情况做适当的变更。另外,在SparkSQL使用上,有几点建议:
- 对于频繁使用的表或查询才进行缓存,对于只使用一次的表不需要缓存;
- 对于join操作,优先缓存较小的表;
- 要多注意Stage的监控,多思考如何才能更多的Task使用PROCESS_LOCAL;
- 要多注意Storage的监控,多思考如何才能Fraction cached的比例更多
## 4:合适的Task ##
对于SparkSQL,还有一个比较重要的参数,就是shuffle时候的Task数量,通过spark.sql.shuffle.partitions来调节。调节的基础是spark集群的处理能力和要处理的数据量,spark的默认值是200。Task过多,会产生很多的任务启动开销,Task多少,每个Task的处理时间过长,容易straggle。