Spark调优:
1、调整资源情况
(1)搭建集群的时候
Spark-env.sh
SPARK_WORKER_CORE
SPARK_WORKER_MEMORY
SPARK_WORKER_INSTANCES
(2)提交任务的时候
spark-submit --master <options>
--executor-cores
--executor-memory
--driver-cores
-driver-memory collect countByKey 这些算子会将Executor的计算结果回收到Driver端
--total-executor-cores 所有的Executor使用的core
conf/spark-default.conf
2、提高任务的并行度
Application
job0
stage0
stage1
stage2
stage0的并行度怎么决定?
(1)如果读取的是HDFS或者本地的file,由split数决定
(2)sc.textFile(path,10) 指定并行度
stage1的并行度怎么决定?
(1)groupByKey,reduceByKey(_+_,10)
(2)repartitions 重新指定并行度
(3)spark.default.parallilism (default:not set)
性能调优-分配更多资源资源:
excutor计算进程所使用的资源
excutor-core
excutor-memory
Driver进程:资源申请,任务分发
driver-memory
dirver-cores
totoal-excutor-core 所有的excutor所能够使用的core的个数
给Spark作业分配更多的资源:
性能调优的王道,就是增加和分配更多的资源,性能和速度上的提升,是显而易见的;
基本上,在一定范围之内,增加资源与性能的提升,是成正比的;写完了一个复杂的spark作业之后,
进行性能调优的时候,首先第一步,我觉得,就是要来调节最优的资源配置;在这个基础之上,
如果说你的spark作业,能够分配的资源达到了你的能力范围的顶端之后,无法再分配更多的资源了,
公司资源有限;那么才是考虑去做后面的这些性能调优的点
问题:
1、分配哪些资源?
2、在哪里分配这些资源?
3、为什么多分配了这些资源以后,性能会得到提升?
答案:
1、分配哪些资源?executor、core per executor、memory per executor、driver memory
2、在哪里分配这些资源?在我们在生产环境中,提交spark作业时,用的spark-submit shell脚本,
里面调整对应的参数
/usr/local/spark/bin/spark-submit \
--class cn.spark.sparktest.core.WordCountCluster \
--num-executors 3 \ 配置executor的数量
--driver-memory 100m \ 配置driver的内存(影响很大)
--executor-memory 100m \ 配置每个executor的内存大小
--executor-cores 3 \ 配置每个executor的cpu core数量
/usr/local/SparkTest-0.0.1-SNAPSHOT-jar-with-dependencies.jar \
3、调节到多大,算是最大呢?
第一种,Spark Standalone,公司集群上,搭建了一套Spark集群,你心里应该清楚每台机器还能够
给你使用的,大概有多少内存,多少cpu core;那么,设置的时候,就根据这个实际的情况,
去调节每个spark作业的资源分配。比如说你的每台机器能够给你使用4G内存,2个cpu core;
20台机器;executor,20;平均每个executor:4G内存,2个cpu core。
第二种,Yarn。资源队列。资源调度。应该去查看,你的spark作业,要提交到的资源队列,
hadoop spark storm 每一个队列都有各自的资源(cpu mem)
大概有多少资源?500G内存,100个cpu core;executor,50;平均每个executor:10G内存,2个cpu core。
Spark-submit的时候怎么指定资源队列? --conf spark.yarn.queue default
设置队列名称:spark.yarn.queue default
一个原则,你能使用的资源有多大,就尽量去调节到最大的大小(executor的数量,几十个到上百个不等;
executor内存;executor cpu core)
4、为什么调节了资源以后,性能可以提升?
增加executor:
如果executor数量比较少,那么,能够并行执行的task数量就比较少,就意味着,
我们的Application的并行执行的能力就很弱。
比如有3个executor,每个executor有2个cpu core,那么同时能够并行执行的task,就是6个。
6个执行完以后,再换下一批6个task。
增加了executor数量以后,那么,就意味着,能够并行执行的task数量,也就变多了。比如原先是6个,
现在可能可以并行执行10个,甚至20个,100个。那么并行能力就比之前提升了数倍,数十倍。
相应的,性能(执行的速度),也能提升数倍~数十倍。
有时候数据量比较少,增加大量的task反而性能会降低,为什么?
增加每个executor的cpu core:
也是增加了执行的并行能力。原本20个executor,每个才2个cpu core。能够并行执行的task数量,
就是40个task。
现在每个executor的cpu core,增加到了5个。能够并行执行的task数量,就是100个task。
执行的速度,提升了2倍左右
SparkContext,DAGScheduler(切割stage,决定task的执行位置,cache persist),TaskScheduler,会将我们的算子,切割成大量的task,
提交到Application的executor上面去执行。
增加每个executor的内存量:
增加了内存量以后,对性能的提升,有三点:
1、如果需要对RDD进行cache,那么更多的内存,就可以缓存更多的数据,将更少的数据写入磁盘,
甚至不写入磁盘。减少了磁盘IO。
2、对于shuffle操作,reduce端,会需要内存来存放拉取的数据并进行聚合。如果内存不够,
也会写入磁盘。如果给executor分配更多内存以后,就有更少的数据,需要写入磁盘,
甚至不需要写入磁盘。减少了磁盘IO,提升了性能。
3、对于task的执行,可能会创建很多对象。如果内存比较小,可能会频繁导致JVM堆内存满了,
然后频繁GC,垃圾回收,minor GC和full GC。(速度很慢)。内存加大以后,带来更少的GC,垃圾回收,
避免了速度变慢,速度变快了。
性能调优-并行度设置:
让我们的程序创建更多的线程去执行
提高并行度的方式有哪一些?
1、如何hdfs上的数据,block个数的增加,HDFS上block size
sc.textFile(path,10)
2、SparkStreaming + kafka receiver模式:block.interval 200ms
Direct模式:读取的topic的分区数
3、reduceByKey groupByKey 指定分区数
4、repartition重分区 = coalesce(,true)
性能调优:
并行度调节
性能调优首先是增加资源,增加Application对应的executor的数量,增加executor里面的cpu core,然后
增加executor里面的内存大小!
这节课也是非常重要的,因为分配完你所能分配的最大资源了!然后对应你的资源调节你程序的并行度!
Spark并行度指的是什么?
Spark作业,Application,Jobs,action(collect)触发一个job,1个job;每个job拆成多个stage,
发生shuffle的时候,会拆分出一个stage,reduceByKey;
stage0
val lines = sc.textFile("hdfs://")
val words = lines.flatMap(_.split(" "))
val pairs = words.map((_,1))
val wordCount = pairs.reduceByKey(_ + _)
stage1
val wordCount = pairs.reduceByKey(_ + _)
wordCount.collect()
reduceByKey,stage0的task,在最后,执行到reduceByKey的时候,会为每个stage1的task,
都创建一份文件(也可能是合并在少量的文件里面);每个stage1的task,会去各个节点上的各个
task创建的属于自己的那一份文件里面,拉取数据;每个stage1的task,拉取到的数据,
一定是相同key对应的数据。对相同的key,对应的values,才能去执行我们自定义的function操作(_ + _)
并行度:其实就是指的是,Spark作业中,各个stage的task数量,也就代表了Spark作业的在各个阶段
(stage)的并行度。
如果不调节并行度,导致并行度过低,会怎么样?
假设,现在已经在spark-submit脚本里面,给我们的spark作业分配了足够多的资源,比如50个executor,
每个executor有10G内存,每个executor有3个cpu core。基本已经达到了集群或者yarn队列的资源上限。
task没有设置,或者设置的很少,比如就设置了,100个task。50个executor,每个executor有3个
cpu core,也就是说,你的Application任何一个stage运行的时候,都有总数在150个cpu core,
可以并行运行。但是你现在,只有100个task,平均分配一下,每个executor分配到2个task,ok,
那么同时在运行的task,只有100个,每个executor只会并行运行2个task。每个executor剩下的一个
cpu core,就浪费掉了。
你的资源虽然分配足够了,但是问题是,并行度没有与资源相匹配,导致你分配下去的资源都浪费掉了。
合理的并行度的设置,应该是要设置的足够大,大到可以完全合理的利用你的集群资源;比如上面的例子,
总共集群有150个cpu core,可以并行运行150个task。那么就应该将你的Application的并行度,
至少设置成150,才能完全有效的利用你的集群资源,让150个task,并行执行;而且task增加到150个以后,
即可以同时并行运行,还可以让每个task要处理的数据量变少;比如总共150G的数据要处理,
如果是100个task,每个task计算1.5G的数据;现在增加到150个task,可以并行运行,
而且每个task主要处理1G的数据就可以。
很简单的道理,只要合理设置并行度,就可以完全充分利用你的集群计算资源,
并且减少每个task要处理的数据量,最终,就是提升你的整个Spark作业的性能和运行速度。
1、task数量,至少设置成与Spark application的总cpu core数量相同(最理想情况,比如总共150个
cpu core,分配了150个task,一起运行,差不多同一时间运行完毕)
2、官方是推荐,task数量,设置成spark application总cpu core数量的2~3倍,比如150个cpu core,
基本要设置task数量为300~500;
实际情况,与理想情况不同的,有些task会运行的快一点,比如50s就完了,有些task,可能会慢一点,
要1分半才运行完,所以如果你的task数量,刚好设置的跟cpu core数量相同,可能还是会导致资源的浪费,
因为,比如150个task,10个先运行完了,剩余140个还在运行,但是这个时候,有10个cpu core就空闲出来了,
就导致了浪费。那如果task数量设置成cpu core总数的2~3倍,那么一个task运行完了以后,
另一个task马上可以补上来,就尽量让cpu core不要空闲,同时也是尽量提升spark作业运行的效率和速度,
提升性能。
3、如何设置一个Spark Application的并行度?
代码层面
spark.default.parallelism
SparkConf conf = new SparkConf()
.set("spark.default.parallelism", "500")
命令参数层面
spark-submit --master --conf spark.default.parallelism=500
性能调优-代码调优:
代码调优:
(1)尽可能复用同一个RDD(一个RDD是另外一个RDD的子集)
举例:val rdd1:RDD<String,String>
val rdd2:RDD<String> = rdd1.map(_._2)
rdd2.filter() = rdd1.filter(_._2)
(2)对多次使用的RDD进行持久化
cache = persist(StorageLevel.MEMORY_ONLY)
persist MEMORY_AND_DISK 持久化的单位是partition
持久化级别的选择:
MEMORY_ONLY 没有数据序列化,内存够
MEMORY_ONLY_SER
MEMORY_AND_DISK_SER 将数据序列化,如果内存放不下,放磁盘
不建议使用DISK_ONLY _2
checkpoint 持久化到磁盘 切断与父RDD的依赖关系
sc.checkpoint(path)
rdd.checkpoint
(3)尽量避免使用shuffle类算子
使用broadcast+filter(map)=join(shuffle)
RDD1.JOIN(RDD2)
(4)使用map-side预聚合(combiner)的shuffle操作
reduceByKey
combineByKey(creatCombiner,mergerValue,mergerCombiner)
(5)使用高性能的算子
mapPartitions map
foreachPartition foreach
repartition(numpartitons)重分区 增加分区 会产生shuffle
filter+coalesce(numparttions,false)不会产生shuffle 减少分区
使用reduceByKey/aggregateByKey(这两个算子都会在Map端进行combiner)替代groupByKey
combiner的好处:
shuffle write写磁盘的时候,数据量就会减少
磁盘文件的数量大大的减少,写磁盘文件的对象就会减少,避免频繁的GC
shuffle read建立通信的次数就大大的减少
更少的数据经过网络传输IO
使用mapPartitions替代普通map
使用foreachPartitions替代foreach
使用filter之后进行coalesce操作
repartition:coalesce(numPartitions,true) 增多分区使用这个
coalesce(numPartitions,false) 减少分区 没有shuffle只是合并partition
(6)广播变量
默认: 如果在Executor中运行的task使用到了外部变量,Driver端中的变量,默认这个外部变量会封装到task对象里面一同发送给Executor
广播变量:
每一个Executor中都存放了一份变量副本(BlockManager),在Executor中执行的task如果使用到了外部变量,直接找本地的BlockManager获取
(7)使用Kryo优化序列化性能
在算子函数中使用到外部变量时,该变量会被序列化后进行网络传输
将自定义的类型作为RDD的泛型类型时(比如JavaRDD<>,SXT是自定义类型),所有自定义类型对象,都会进行序列化。因此这种情况下,也要求自定义的类必须实现Serializable接口。
使用可序列化的持久化策略时(比如MEMORY_ONLY_SER),Spark会将RDD中的每个partition都序列化成一个大的字节数组。
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.registerKryoClasses(new Class[]{
SpeedSortKey.class})
(8)优化数据结构
(9)使用高性能的库fastutil