Spark调优

来源:互联网 发布:罗技g303编程 编辑:程序博客网 时间:2024/06/17 06:25

Spark,Hadoop交流群,群QQ号:521066396,欢迎加入共同学习,一起进步~

Spark运行原理

这里写图片描述

四个阶段:

1、准备数据集合和生成相应的RDD DAG图,发送给DAGScheduler
2、DAGScheduler在收到数据集的DAG之后,详细的组织该DAG所包含的stage,将这些stage所包含的TaskSet发送给TaskScheduler
3、TaskScheduler收到了TaskSet任务集合,将TaskSet任务集合进行细分成每一个Task提交给Worker执行
4、Worker在收到具体的Task之后,启动executor进程,该进程开始启动对应Task线程执行任务
备注:Spark是根据shuffle类算子来进行stage的划分。如果我们的代码中执行了某个shuffle类算子(比如reduceByKey、join等),那么就会在该算子处,划分出一个stage界限来。可以大致理解为,shuffle算子执行之前的代码会被划分为一个stage,shuffle算子执行以及之后的代码会被划分为下一个stage。最消耗性能的地方就是shuffle过程。shuffle过程,简单来说,就是将分布在集群中多个节点上的同一个key,拉取到同一个节点上,进行聚合或join等操作。比如reduceByKey、join等算子,都会触发shuffle操作。
这里写图片描述
1、生成相应的RDD DAG图
2、根据数据输入和算子类型,来决定作业划分Stage的数目以及Task的数目。
3、任务调度
4、分布式执行Task
参考:https://www.iteblog.com/archives/1659

一、优化1:设置合适的资源

这里写图片描述

1、资源量

(1)是否充分使用了集群中的资源
(2)Executor个数,每个Executor的内存和core等

2、增加资源

(1)增加Executor个数(–num-executors 4)
(2)增加每个executor可同时运行的task数目(–executor-cores 2)
这里写图片描述
默认Executor为2个,task为1个。
参考:http://blog.csdn.net/u012102306/article/details/51637366

3、调整任务并行度

多少个并行的任务
(1)Map任务并行度
(2)Reduce任务并行度
适当调整任务数目,默认一个分区对应一个task
(1)Map个数:默认与hdfs block/hbase region数目一致
把map数目调大将每个map处理数据量调小,默认128M
sc.textFile(“/input/data”,100)
把map数目调大(将每个map处理数据量调小,默认128M)
–conf spark.hadoop.mapreduce.input.fileinputformat.split.minsize=100000000
sc.hadoopConfiguration.set(“mapreduce.input.fileinputformat.split.minsize”,”100000000”)
实际上Spark集群的资源并不一定会被充分利用到,所以要尽量设置合理的并行度,来充分地利用集群的资源。才能充分提高Spark应用程序的性能。
Spark会自动设置以文件作为输入源的RDD的并行度,依据其大小,比如HDFS,就会给每一个block创建一个partition,也依据这个设置并行度。对于reduceByKey等会发生shuffle的操作,就使用并行度最大的父RDD的并行度即可。
可以手动使用textFile()、parallelize()等方法的第二个参数来设置并行度;也可以使用spark.default.parallelism参数,来设置统一的并行度。Spark官方的推荐是,给集群中的每个cpu core设置2~3个task。
常用参数
● num-executors
  参数说明:该参数用于设置Spark作业总共要用多少个Executor进程来执行。Driver在向YARN集群管理器申请资源时,YARN集群管理器会尽可能按照你的设置来在集群的各个工作节点上,启动相应数量的Executor进程。这个参数非常之重要,如果不设置的话,默认只会给你启动少量的Executor进程,此时你的Spark作业的运行速度是非常慢的。
  参数调优建议:每个Spark作业的运行一般设置50~100个左右的Executor进程比较合适,设置太少或太多的Executor进程都不好。设置的太少,无法充分利用集群资源;设置的太多的话,大部分队列可能无法给予充分的资源。
● executor-memory
  参数说明:该参数用于设置每个Executor进程的内存。Executor内存的大小,很多时候直接决定了Spark作业的性能,而且跟常见的JVM OOM异常,也有直接的关联。
  参数调优建议:每个Executor进程的内存设置4G~8G较为合适。但是这只是一个参考值,具体的设置还是得根据不同部门的资源队列来定。可以看看自己团队的资源队列的最大内存限制是多少,num-executors乘以executor-memory,就代表了你的Spark作业申请到的总内存量(也就是所有Executor进程的内存总和),这个量是不能超过队列的最大内存量的。此外,如果你是跟团队里其他人共享这个资源队列,那么申请的总内存量最好不要超过资源队列最大总内存的1/3~1/2,避免你自己的Spark作业占用了队列所有的资源,导致别的同学的作业无法运行。
● executor-cores
  参数说明:该参数用于设置每个Executor进程的CPU core数量。这个参数决定了每个Executor进程并行执行task线程的能力。因为每个CPU core同一时间只能执行一个task线程,因此每个Executor进程的CPU core数量越多,越能够快速地执行完分配给自己的所有task线程。
  参数调优建议:Executor的CPU core数量设置为2~4个较为合适。同样得根据不同部门的资源队列来定,可以看看自己的资源队列的最大CPU core限制是多少,再依据设置的Executor数量,来决定每个Executor进程可以分配到几个CPU core。同样建议,如果是跟他人共享这个队列,那么num-executors * executor-cores不要超过队列总CPU core的1/3~1/2左右比较合适,也是避免影响其他同学的作业运行。
● driver-memory
  参数说明:该参数用于设置Driver进程的内存。
  参数调优建议:Driver的内存通常来说不设置,或者设置1G左右应该就够了。唯一需要注意的一点是,如果需要使用collect算子将RDD的数据全部拉取到Driver上进行处理,那么必须确保Driver的内存足够大,否则会出现OOM内存溢出的问题。
● spark.default.parallelism
  参数说明:该参数用于设置每个stage的默认task数量。这个参数极为重要,如果不设置可能会直接影响你的Spark作业性能。
  参数调优建议:Spark作业的默认task数量为500~1000个较为合适。很多同学常犯的一个错误就是不去设置这个参数,那么此时就会导致Spark自己根据底层HDFS的block数量来设置task的数量,默认是一个HDFS block对应一个task。通常来说,Spark默认设置的数量是偏少的(比如就几十个task),如果task数量偏少的话,就会导致你前面设置好的Executor的参数都前功尽弃。试想一下,无论你的Executor进程有多少个,内存和CPU有多大,但是task只有1个或者10个,那么90%的Executor进程可能根本就没有task执行,也就是白白浪费了资源!因此Spark官网建议的设置原则是,设置该参数为num-executors * executor-cores的2~3倍较为合适,比如Executor的总CPU core数量为300个,那么设置1000个task是可以的,此时可以充分地利用Spark集群的资源。
这里写图片描述
这里写图片描述
参考文献:http://blog.csdn.net/qq1010885678/article/details/48505701

4、修改存储格式

(1)当前采用的数据存储格式
文本文件?
为什么文本文件不好?比如,一个文件有1000列,只需要用到10列。
(2)列式存储格式
parquet,大大节省了存储空间。
这里写图片描述
这里写图片描述
参考:http://www.ibm.com/developerworks/cn/analytics/blog/ba-parquet-for-spark-sql/index.html
http://blog.csdn.net/sundujing/article/details/51438306(Spark SQL下的Parquet使用最佳实践和代码实战)

二、辅助工具

jstack,jstat,jprofile
Spark history server:查看运行完成作业的信息和日志

三、程序调优

1、Spark运行环境:存储和计算资源
2、优化RDD操作符的使用方法
3、配置参数优化

四、运行环境优化

Case 1:防止不必要的jar包上传与分发

(1)将系统jar包上传到hdfs上,直接使用hdfs上的文件
在spark-defaults.conf添加属性配置:
spark.yarn.jar=hdfs://bdenode1:9000/spark_lib/spark-assembly-1.6.2-hadoop2.4.0.jar
(2)将用户应用程序jar包上传到hdfs上,防止重复分发
这里写图片描述
这里写图片描述

Case2:提高数据的本地性(Data Locality)

Spark只负责计算,而存储可能在其他节点上,因此需要通过网络传输。
(1)数据本地性非常重要
(2)什么是数据本地性(Data Locality)
● 如果任务运行在它将处理的数据所在的节点,则称该任务具有“数据本地性”
● 本地性可避免节点或机架数据传输,提高运行效率
(3)提高数据的本地性
● 计算和存储同节点部署(yarn,hdfs)
● 增加executor数目(对于节点个数比较多的)
● 增加数据副本数(hdfs默认3份)
● 调整spark.locality.wait(默认:3s),spark.locality.wait.process,spark.locality.wait.node,spark.locality.wait.rack
(4)数据本地性分类
● 同节点(node-local)
● 同机架(rack-local)
● 其他(off-switch)
这里写图片描述

Case3:存储格式选择

(1)行式存储和列式存储
列式存储优势:减少读IO量,占用存储空间少(压缩比高)
(2)列式存储格式
ORC(http://orc.apache.org/),源于Hive
Parquet(http://parquet.apache.org/),跟MapReduce/Spark/…等有良好的集成
这里写图片描述

Case4:将Spark运行在高配置机器上

(1)YARN基于标签的高度
● Hadoop 2.6.0开始,提供了基于标签的调度策略
● http://hadoop.apache.org/docs/r2.7.3/hadoop-yarn/hadoop-yarn-site/NodeLabel.html
(2)思路
● 将一些高配置机器打上spark-node/streaming-node标签,并结合队列配置(比如spark队列)使之生效。
● 将所有spark程序提交到spark队列中
● spark-submit -queue spark…

五、Spark调优思路2:优化操作符

Case1:避免创建重复的RDD

这里写图片描述

Case2:过滤操作导致产生很多小任务

这里写图片描述
解决方案:对数据过滤后,使用coalesce(默认不进行shuffle,推荐使用)或者repartition(默认进行shuffle)操作符合并若干个分片(Partition)

Case3:降低单条记录处理开销

这里写图片描述
解决方案:使用mapPartitions或者mapWith操作符
这里写图片描述

Case4:处理数据倾斜或者任务倾斜

数据倾斜只会发生在shuffle过程中。
解决方案:
● 选择合理的Partition key
● 克服慢节点导致任务运行缓慢(慢节点磁盘老化,负载过重等)
将spark.speculation设置为true
将存在问题的节点从集群中剔除
参考:https://www.iteblog.com/archives/1671

Case5:对复用的RDD进行缓存

这里写图片描述
每次RDD需要重算,代价大
解决方案:对RDD进行缓存
这里写图片描述

Case6:操作符的选择

(1)避免使用Cartesian
(2)尽可能避免shuffle
(3)如果可能,reduceByKey或者aggregateByKey算子来替代groupByKey
● rdd.groupByKey().mapValues(_.sum)
● rdd.reduceByKey(+)
(4)如果可能,用treeReduce代替reduce
(5)输入输出value类型不同时,避免使用reduceByKey
问题:将key相同的value聚集到一起,并去重相同的value
这里写图片描述

Case7:一个Spark应用程序由多个Job组成,如果Job之间没有依赖关系,可并行处理(充分利用资源 )

● 启动FIAR调度器:spark.scheduler.mode=fair
● 将action相关操作放到单线程中
这里写图片描述
这里写图片描述

Case8:广播大变量

如果使用的外部变量比较大,建议使用Spark的广播功能,对该变量进行广播。广播后的变量,会保证每个Executor的内存中,只驻留一份变量副本,而Executor中的task执行时共享该Executor中的那份变量副本。这样的话,可以大大减少变量副本的数量,从而减少网络传输的性能开销,并减少对Executor内存的占用开销,降低GC的频率。
参考:https://www.iteblog.com/archives/1657

六、Spark调优思路3:作业参数调优

Case1:设置合适的资源量

(1)Executor数目并非越多越好
● Task数目不小于Executor的core总数
● 过多的executor会导致资源浪费
● 举例
1GB数据(hdfs block大小为128M),默认产生8个task
申请8个executor,每个2个core,一共16个core(浪费一倍资源)

Case2:设置合适的JVM参数

(1)内存参数
(2)Log4j以及lib路径参数
(3)定制化JDK版本

Case3:启动更高效的序列化方法

(1)为什么序列化很重要?
(2)默认采用Java序列化方法,速度缓慢
(3)建议启用Kyro序列化方法
● 兼容性差
● 启动方法:将spark.serializer设置为org.apache.spark.serializer.KrvoSerializer
Spark默认使用的是Java的序列化机制,也就是ObjectOutputStream /ObjectInputStream API来进行序列化和反序列化。但是Spark同时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序列化类库的性能要高很多。官方介绍,Kryo序列化机制比Java序列化机制,性能高10倍左右。Spark之所以默认没有使用Kryo作为序列化类库,是因为Kryo要求最好要注册所有需要进行序列化的自定义类型,因此对于开发者来说,这种方式比较麻烦。

Case4:增大offheap内存

(1)问题:Spark On YARN模式下,executor为什么经常被YARN杀掉?

(2)建议增大overhead(off-heap)内存大小 (默认memory*0.1)
● 设置spark.yarn.executor.memoryOverhead
● spark.yarn.driver.memoryOverhead

Case5:Shuffle参数调优

(1)Shuffle实现的选择
● Hash-based Shuffle:每个executor产生多个文件
● Sort-based Shuffle:每个Map Task产生一个文件,更省内存的实现
● 如何选择?Reduce Task数目较多时,选择Sort-based实现。(修改spark.shuffle.manager,可选值:hash或sort)
(2)默认情况下,Reduce Task收到的数据会存到内存(HashTable)中
● 防止Reduce Task OOM,可将spark.shuffle.spill设置为true
● Spillt条件,可通过spark.shuffle.memoryFraction设置(默认0.3)
参考:https://www.iteblog.com/archives/1672

Case6:设置Reduce Task数目

(1)Reduce Task数目过小,运行过慢,且可能导致OOM
(2)Reduce Task数目过大,产生较多的小任务,启动和调试开销增大
● 显示设置Reduce Task数目,比如groupByKey,reduceByKey等均提供了设置参数
● 修改默认参数值spark.default.parallelism,默认是跟前一个阶段一致。
Spark SQL(DataFrame/DataSet)是Spark的未来
(1)越来越多的程序会采用SparkSQL编写,而不是Spark Core API
(2)为何采用Spark SQL写
● 更加简单。DataFrame/DataSet是更高级的API,而RDD API比较低级
● 理加高效。Spark SQL自带了优化器,可以自动优化程序
http://blog.cloudera.com/blog/2015/03/how-to-tune-your-apache-spark-jobs-part-1/
http://blog.cloudera.com/blog/2015/03/how-to-tune-your-apache-spark-jobs-part-2/