Spark调优

一.程序编写调优

1.复用RDD

我们大家都知道,Spark是通过读取数据形成RDD,从而对数据进行转换和落地的。通常spark读取数据的速度要比数据计算的速度要慢,如果一份数据,程序里已经读取形成了RDD,你想把这个RDD进行多次的处理,直接复用就好,一定不要再次将数据读取形成RDD。这个是用户程序中容易忽略的一点。

 

2.如果RDD被多次进行转换和执行(transform & action)操作,建议对RDD进行持久化。

Spark中由于对某个RDD的action操作触发作业的时候,会基于Lineage从后往前推,一直找到该RDD的源头RDD,然后从前往后计算出结果。如果我们程序里面对某个RDD执行了多次transform和action操作,每次action触发作业都会重新从源头RDD计算来获得该RDD,性能明显比较低。但如果我们将多次使用的RDD进行持久化操作,那么spark会将RDD保存到内存或者磁盘里,以后每次对该RDD进行操作的时候直接从内存或磁盘读取数据,而不是从头计算,性能会提高很多。

 

3.使用高性能算子

3.1   reduceByKey/aggregateByKey 替代 groupByKey

reduceByKey和aggregateByKey算子会使用用户自定义的函数对每个节点本地含有相同key的记录进行预聚合,预聚合以后,每个节点本地就只有一条含有相同key的记录,其他节点在拉取所有节点上含有相同key的记录时,就会大大减少需要拉取的数据量,从而减少磁盘I/O和网络传输开销。而groupByKey是不会进行预聚合的,所有的数据都会在集群中分发和传输,性能相比是差很多。

 

3.2  mapPartitions替代普通map

mapPartitions一次函数会处理一个partition中所有的数据,而不是像map函数一样,一次只处理一条数据,所以性能会高。(注:如果每个partition数据量很大,则不建议使用此方法,因为mapPartition会一次性处理一个partition的所有数据,如果数据量很大,而分给task的内存不够,垃圾回收无法回收太多对象就会出现OOM)

 

3.3  foreachPartitions 替代 foreach

原理类似mapPartitions替代map,也是一次函数处理一个partition所有数据。

 

3.4  repartitionAndSortWithinPartition 替代 Repartition 和Sort操作

如果数据在Repartition重新分区以后,还需要进行sort排序操作,则建议直接使用repartitionAndSortWithinPartition算子,因为该算子可以一边进行重新分区,一边进行排序操作,并行操作性能会有所提高。

 

4. 尽早利用filter过滤不需要的RDD数据

在数据处理的过程中,我们需要的数据很多时候并不是spark读进的所有数据,那我们需要尽早的过滤不需要的数据,减少内存的使用量。如果RDD通过filter进行过滤后,可以去除大概30%以上的数据,那我们则需要使用coalesce算子,将过滤后的RDD数据压缩到更少的Partition中,减少并行度,避免过多的task的开销。因为经过过滤后的RDD数据比以前少了30%,一个partition对应一个task任务,task的创建和销毁也是有开销的,将数据压缩到更少的partition中去,一方面可以最大程度利用task的资源,另一方面减少了task的创建和销毁所带来的额外开销。

 

5.利用广播机制(broadcast)

我们在开发spark程序的过程中,有时需要在算子函数中使用到外部大的变量(例如HDFS上的一份100M数据形成的变量),那么我们要用broadcast功能提升性能。因为spark算子函数在用到外部变量的时候,会将此变量复制多个副本,通过网络传输到每个task。如果外部变量较大的时候,每个task传输一份,会占用大量的网络资源和每个task的内存资源,可能会导致executor进行频繁的GC操作,极大的影响性能。如果我们使用了广播机制,广播后的变量则会保证每个executor只会保留一份变量副本,这个executor内的所有task则会共享这份变量,减少了对内存的占用,降低GC频率。

 

二.资源参数调优

Spark的应用程序的每个executor都有固定的core和内存,每个executor的内存,可以通过spark-submit的--executor-memory 参数来指定,内存设置的大小有时候直接决定了spark作业的性能。Executor的个数和每个executor 的core个数可以通过--num-executors和--executor-cores参数来指定,每个core同一时间只能执行一个task线程,所以每个executor的core数量越多,越能快速的执行完分配给自己的task线程,建议为每个executor设置5个core。但也不是说为了快速完成自己的作业,将以上参数设置的很大是最佳选择,因为每个用户在yarn上提交作业的时候,都会指定所属队列,每个队列所分配的资源都是有限的,如果任务本身的数据量不大,在提交任务的时候指定太多的资源,只会造成资源的浪费,同时也影响同一队列其他用户的任务提交,一定要根据自己作业的数据量合理设置参数值。如果spark作业在运行中出现OOM,一种方法是适量调大--executor-memory,同时可以通过--conf spark.yarn.executor.memoryOverhead将此参数稍微调大一些,这个参数是留给vm的开销,建议值为1000MB。

 

三.序列化

序列化是shuffle和cache的瓶颈,合理的设置序列化既能提高I/O性能,还可以减少内存的使用,spark默认的序列化器是org.apache.spark.serializer.JavaSerializer,但是这个序列化器的性能和空间表现能力都比较差。Spark支持使用Kryo序列化器org.apache.spark.serializer.KryoSerializer,此序列化器更快,压缩率更高。根据Spark官方的测试结果,Kryo序列化机制比Java序列化机制性能提高了10倍左右。但是Kryo并不支持所有可序列化的类型,且要求注册所有需要进行序列化的自定义类型,对于开发者来说,这种机制有些麻烦,但是在网络I/O密集的应用中使用Kryo是非常好的选择,Spark也大多数常用的scala类都自动的包含了Kryo序列化库。下面是使用Kryo序列化器的Demo代码:

 

val  conf = new SparkConf()

conf.set(“spark.serializer”,”org.apache.spark.serializer.KryoSerializer”)

conf.set(“spark.kryo.registrationRequired”,”true”)

conf.registerKryoClasses(Array(classOf[ClassA], classOf[ClassB]))

 

用户可以自己测试一下使用Java 和Kryo序列化器的性能差异,有助于更好的编写spark应用。

 

四 Shuffle调优

在Spark程序中,shuffle是性能的最大瓶颈,因为shuffle一般伴随着磁盘I/O和网络I/O的开销,这也是编写spark程序时最好避免使用需要shuffle算子的原因。下面给出一些关于shuffle的参数讲解。

1.      spark.shuffle.file.buffer

将数据文件写入磁盘之前,会将数据先写入Buffer缓冲区中,等到缓冲区写满以后才会溢写到磁盘中,默认值为32KB,这个参数就是指的Buffer缓冲区大小。调优建议:如果作业可以的内存资源比较充足,则可以适当调大这个参数大小,从而减少shuffle  write过程中溢出写磁盘的次数,减少磁盘I/O次数。

 

2.     spark.reducer.maxSizeInFlight

此参数用于设置shuffle read task的Buffer缓冲区大小,这个buffer决定了每次能够拉取多少数据。如果作业可用的内存资源充足,建议调大此参数大小(默认是48MB),调大此参数会减少拉取数据的次数,从而减少网络传输的次数,提升性能。

 

3.      spark.local.dir

Shuffle的writer阶段使用的是本地磁盘目录,当shuffle时磁盘I/O的时间过长时,可用将此参数设置为多个I/O速度快的磁盘,通过增加I/O来优化shuffle的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值