13.1 Spark调优

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011418530/article/details/82219804

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

猜你喜欢

转载自blog.csdn.net/u011418530/article/details/82219804
今日推荐