Spark性能优化详解

1.对集群分配更多的资源
在提交任务时,在–total-executor-cores,–executor-memory,–driver-memory参数上分配
分配cpu core,memory
给executor分配更多的内存,能够减少executor频繁gc,因为一旦发生频繁gc,spark的性能会马上下降
给executor分配更多的内存,会将尽量多的rdd的数据保存在内存中,可以避免磁盘IO
给executor分配更多的内存,可以减少shuffle阶段数据落地。
分配更多的cpu core,可以提高任务运行的并行度
这里写图片描述


2.调节任务并行度
我们在给任务分配更多的资源的时候,意味着任务能够具备更多的资源来执行,例如,给任务分配了5个executor,每个executor有10个core,那么整个任务有50个core,意味着同一时间能够并行执行的任务是50个,当我们的任务task只有20,那么还有30个core是空闲的浪费了,即任务的数量不能满足并行度。
针对这种情况,我们应该调节任务的数量,提高任务的并行度。
如何提高任务的并行度:
(1)调节shuffle阶段任务的并行度,一般shuffle算子,有两个参数,后面一个参数就是用来调节并行度的。
(2)调节rdd任务的分区数,可以使用该算子–coalesce
(3)可以通过调节spark.default.parallelism这个参数来调节并行度


3.对公用的rdd进行持久化
如果一个rdd会被多次引用到,并且这个rdd计算过程复杂,计算时间特别耗时,调用rdd.persist或cache方法进行持久化


4.广播大变量
如果每个task都需要拷贝一个副本到executor去执行,如果有1000个task在某个worker上执行,而这个副本有100M,那么意味着需要拷贝100G的数据到某个worker上去执行,这样会大大消耗网络流量,同时会加大executor的内存消耗,从而增加了spark作业的运行时间,降低了spark作业的运行效率,增加了作业失败的效率。
当rdd引用了一个外部变量并且这个外部变量数据量不小,同时这个rdd对应的task数量特别多,那么就可以使用广播变量。
我们将这种大的外部变量做成广播变量的时候,每个executor的内存中只会有一个外部变量的副本,这个副本对于所有的task都是共享的,这样减少了网络流量消耗,降低了executor的内存消耗,提高了spark作业运行效率,缩短了运行时间


5.使用kryo序列化
默认情况下,spark内部是使用java的序列化机制,这种默认序列化的好处在于处理起来比较方便,不需要手动操作,只是在算子里使用的变量必须是实现Serializable接口,并且可序列化的
缺点在于默认的序列化机制的效率不高,序列化的速度慢,序列化以后的数据占用的内存空间相对较大
使用kryo序列化机制比默认的速度快,序列化后的数据更小,大概是java序列化机制的1/10
所以使用kryo序列化后,可以让网络传输的数据变少,在集群中耗费的内存资源大大减少
Kryo序列化机制,它一旦启动以后,会生效的几个地方:
①算子函数中使用到的外部变量
算子函数中使用到的外部变量,使用Kryo以后:优化网络传输的性能,可以优化集群中内存的占用和消耗
②持久化RDD时进行序列化,StorageLevle.MEMORY_ONLY_SER
持久化RDD,优化内存的占用和消耗;持久化RDD占用的内存越少,task执行的时候,创建的对象,它就不至于频繁的占满内存,频繁发生GC
③shuffle
shuffle:可以优化网络传输的性能
Kryo之所以没有被作为默认的序列化类库的原因,就要出现了:主要是因为Kryo要求,如果要达到它的最佳性能的话,那么久一定要注册一自定义的类(就比如,你的算子函数中使用到了外部自定义类型的对象变量,这时,就要求必须注册你的类,否则Kryo达不到最佳性能)
首先第一步,在SparkConf中设置一个属性,spark.serializer,org.apache.spark.serializer.KryoSerializer类;
SparkConf.set(“spark.serializer”,”org.apache.spark.serializer.KryoSerializer”)
第二步,注册你使用到的,需要通过Kryo序列化的,一些自定义类,SparkConf.registerKryoClass()
sparkConf.registerKryoClasses(Array(classOf[CategorySecondSort]))


6.减少shuffle的发生
尽量避免使用会发生shuffle的算子
在数据源头对数据提前进行聚合


7.优化数据结构
避免对象套对象
减少数据集合的使用,尽量使用数组。
因为对象需要序列化,减少对象的使用,尽量使用字符串拼接
可以使用第三方提供的占用内存小,序列化速度快的数据结构类库


8.避免数据倾斜
在进行shuffle的时候,必须将各个节点上相同的key拉取到某个节点上的一个task来进行处理,比如按照key进行聚合或join操作,如果某个key对应的数据量特别大的话,就会发送数据倾斜。如大部分key对应10条数据,但是个别key却对应100万条数据,那么大部分task可能就只会分配到10条数据,然后1秒就运行完,个别task可能分配到10万数据,要运行一两个小时,因此,整个spark作业度是由运行时间最长的那个task决定的。
数据倾斜只会发生在shuffle中。可能会发生shuffle的算子:groupByKey,reduceByKey,join等。
解决方案:
(1)使用hive ETL预处理数据
使用场景:导致数据倾斜的是hive表,如果该hive表中的数据本身很不均匀,而且业务场景需要频繁使用spark对hive表执行某个分析操作,那么比较适合使用这种技术方案。
实现思路:评估一下是否可以通过hive来进行数据预处理,然后在spark作业中针对的数据源就不是原来的hive表了,而是处理后的hive表,此时由于数据已经预先经过聚合或join操作,那么在spark作业中也就不需要使用原先的shuffle算子执行这类操作了。
实现原理:从根源上解决了数据倾斜,因为彻底避免了在spark中执行shuffle类算子,但是这种方式属于治标不治本,只是把数据倾斜的发生提前到了hive ETL中,避免spark程序发生数据倾斜而已。
(2)过滤少数导致倾斜的key
使用场景:如果发现导致倾斜的key就少数几个,而且对计算本身的影响不大的话就适用这种方案。
实现思路:判断那少数几个数据量特别多的key,对作业的执行和计算结果不是特别重要的话,就干脆直接过滤到那几个少数key。
比如,在spark SQL中可以使用where字句过滤掉这些key或者在sparkRDD执行filter算子过滤掉这些key。
实现原理:将导致数据倾斜的key过滤掉之后,这些key就不会参与计算了,自然不可能产生数据倾斜。
优点:实现简单,效果也好,可以完全避免掉数据倾斜。
缺点:适用场景不多,大多数情况下,导致倾斜的key还是很多,并不是只有少数几个。
(3)提高shuffle操作的并行度
使用场景:必须要对数据倾斜进行解决,就建议使用这种方案,因为这是处理数据倾斜最简单的一种方案。
实现思路:在对RDD执行shuffle算子时,给shuffle算子传入一个参数,该参数就设置了这个shuffle算子执行时shuffle read task的数量。
实现原理:增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个task,从而让每个task处理比原来更少的数据。
(4)两阶段聚合(局部聚合+全局聚合)
对rdd执行reduceByKey等聚合类shuffle算子或者在sparkSQL中使用group by语句进行分组聚合时,比较适用这种方案。
实现思路:进行两阶段聚合,第一次是局部聚合,先给每个key都打上一个随机数,接着对打上随机数后的数据执行reduceByKey等聚合操作,进行局部聚合,然后将各个key的前缀去掉,再次进行全局聚合操作,就可以得到最终结果。
实现原理:将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题,接着去除掉随机前缀,再次进行全局聚合,就可以得到最终结果


9.开启推测机制
推测机制后,如果集群中某一台机器的几个task特别慢,推测机制会将任务分配到其他机器执行,最后spark会选取最快的作为最终结果


10.算子使用技巧
非必要情况下,不要使用collect
创建连接对象尽量在分区上创建

阅读更多

没有更多推荐了,返回首页