Spark性能优化点(一)

1.分配更多的资源

   1.1.增加executor

    1.2.增加每个executor的cpu core

          增加executor的并行能力,一个cpu core运行一个task

    1.3.增加每个executor的内存

      1).如果需要对RDD进行cache,那么更多的内存,就可以缓存更多的数据,将更少的数据写入磁盘,甚至不写入磁盘。减少了       磁盘IO

     2). 对于shuffle操作,reduce端,会需要内存来存放拉取的数据并进行聚合。如果内存不够,也会写入磁盘。如果给executor分          配更多内存以后,就有更少的数据,需要写入磁盘,甚至不需要写入磁盘。减少了磁盘IO,提升了性能。

     3).对于task的执行,可能会创建很多对象。如果内存比较小,可能会频繁导致JVM堆内存满了,然后频繁GC,垃圾回收,                 minor GCfull GC。(速度很慢)。内存加大以后,带来更少的GC,垃圾回收,避免了速度变慢,速度变快了。

2.调节spark的并行能力

   2.1. Task数量,至少设置成与Spark application的总cpu core数量相同(最理想情况,比如总共150cpu core,分配了150task,一     起运行,差不多同一时间运行完毕)

   2.2 官方是推荐,task数量,设置成spark applicationcpu core数量的2~3倍,比如150cpu core,基本要设置task数量为300~500

  2.3.设置方法:

spark.default.parallelism

SparkConf conf = new SparkConf()   .set("spark.default.parallelism", "500")

3.RDD重构

3.1.RDD架构重构与优化

     尽量去复用RDD,差不多的RDD,可以抽取称为一个共同的RDD,供后面的RDD计算时,反复使用。

3.2.公共RDD一定要实现持久化

     持久化要根据不同场景选择cache或persist方法

3.3.持久化,是可以进行序列化的

     主要是指persist方法,选择不同的持久化方式

     1).纯内存MEMORY_ONLY:如果正常将数据持久化在内存中,那么可能会导致内存的占用过大,这样的话,也许,会导致OOM内存溢出。

     2).MEMORY_ONLY_SER序列化内存存储:当纯内存无法支撑公共RDD数据完全存放的时候,就优先考虑,使用序列化的方式在纯内存中存储。将RDD的每个partition的数据,序列化成一个大的字节数组,就一个对象;序列化后,大大减少内存的空间占用。

     序列化的方式,唯一的缺点就是,在获取数据的时候,需要反序列化。

     3).内存+磁盘MEMORY_AND_DISK 或MEMORY_AND_DISK_SER

     如果序列化纯内存方式,还是导致OOM,内存溢出;就只能考虑磁盘的方式,内存+磁盘的普通方式(无序列化)。

    MEMORY_AND_DISK :使用未序列化的Java对象格式,优先尝试将数据保存在内存中。如果内存不够存放所有的数据,会       将数据写入磁盘文件中,下次对这个RDD执行算子时,持久化在磁盘文件中的数据会被读取出来使用。

      MEMORY_AND_DISK_SER:基本含义同MEMORY_AND_DISK。唯一的区别是,会将RDD中的数据进行序列化,RDD的        每个partition会被序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。

     4).DISK_ONLY纯磁盘

       使用未序列化的Java对象格式,将数据全部写入磁盘文件中。

     5).MEMORY_ONLY_2, MEMORY_AND_DISK_2, 等等双副本机制,为了数据的高可靠性,而且内存充足,可以使用双副本        机制,进行持久化

       对于上述任意一种持久化策略,如果加上后缀_2,代表的是将每个持久化的数据,都复制一份副本,并将副本保存到其他节点上。这种基于副本的持久化机制主要用于进行容错。假如某个节点挂掉,节点的内存或磁盘中的持久化数据丢失了,那么后续对RDD计算时还可以使用该数据在其他节点上的副本。如果没有副本的话,就只能将这些数据从源头处重新计算一遍了。

4.使用广播变量

  广播变量,初始的时候,就在Drvier上有一份副本。

   task在运行的时候,想要使用广播变量中的数据,此时首先会在自己本地的Executor对应的BlockManager中,尝试获取变量副本;如果本地没有,那么就从Driver远程拉取变量副本,并保存在本地的BlockManager中;此后这个executor上的task,都会直接使用本地的BlockManager中的副本。executorBlockManager除了从driver上拉取,也可能从其他节点的BlockManager上拉取变量副本,举例越近越好。

5.优化序列化方法

默认情况下,Spark内部是使用Java的序列化机制,ObjectOutputStream / ObjectInputStream,对象输入输出流机制,来进行序列化。这种默认序列化机制的好处在于,处理起来比较方便;也不需要我们手动去做什么事情,只是,你在算子里面使用的变量,必须是实现Serializable接口的,可序列化即可。

但是缺点在于,默认的序列化机制的效率不高,序列化的速度比较慢;序列化以后的数据,占用的内存空间相对还是比较大。

可以手动进行序列化格式的优化Spark支持使用Kryo序列化机制。Kryo序列化机制,比默认的Java序列化机制,速度要快,序列化后的数据要更小,大概是Java序列化机制的1/10

所以Kryo序列化优化以后,可以让网络传输的数据变少;在集群中耗费的内存资源大大减少。

5.1.Kryo序列化机制,一旦启用以后,会生效的几个地方:

   1、算子函数中使用到的外部变量,使用Kryo以后:优化网络传输的性能,可以优化集群中内存的占用和消耗

   2、持久化RDD,优化内存的占用和消耗;持久化RDD占用的内存越少,task执行的时候,创建的对象,就不至于频繁的占满       内存,频繁发生GC

   3shuffle:可以优化网络传输的性能

6.使用fastutil扩展的集合框架

6.1.fastutil介绍

fastutil是扩展了Java标准集合框架(MapListSetHashMapArrayListHashSet)的类库,提供了特殊类型的mapsetlistqueue

fastutil能够提供更小的内存占用,更快的存取速度;我们使用fastutil提供的集合类,来替代自己平时使用的JDK的原生的MapListSet,好处在于,fastutil集合类,可以减小内存的占用,并且在进行集合的遍历、根据索引(或者key)获取元素的值和设置元素的值的时候,提供更快的存取速度;

fastutil也提供了64位的arraysetlist,以及高性能快速的,以及实用的IO类,来处理二进制和文本类型的文件;

fastutil最新版本要求Java 7以及以上版本;

6.2.Spark中应用fastutil的场景:

1、如果算子函数使用了外部变量;那么第一,你可以使用Broadcast广播变量优化;第二,可以使用Kryo序列化类库,提升序列化性能和效率;第三,如果外部变量是某种比较大的集合,那么可以考虑使用fastutil改写外部变量,首先从源头上就减少内存的占用,通过广播变量进一步减少内存占用,再通过Kryo序列化类库进一步减少内存占用。

2、在你的算子函数里,也就是task要执行的计算逻辑里面,如果有逻辑中,出现,要创建比较大的MapList等集合,可能会占用较大的内存空间,而且可能涉及到消耗性能的遍历、存取等集合操作;那么此时,可以考虑将这些集合类型使用fastutil类库重写,使用了fastutil集合类以后,就可以在一定程度上,减少task创建出来的集合类型的内存占用。避免executor内存频繁占满,频繁唤起GC,导致性能下降。

6.3.fastutil的使用  

第一步:在pom.xml中引用fastutil的包

<dependency>

    <groupId>fastutil</groupId>

    <artifactId>fastutil</artifactId>

    <version>5.0.9</version>

</dependency>

速度比较慢,可能是从国外的网去拉取jar包,可能要等待5分钟,甚至几十分钟,不等

List<Integer> => IntList

基本都是类似于IntList的格式,前缀就是集合的元素类型;特殊的就是MapInt2IntMap,代表了key-value映射的元素类型。除此之外,刚才也看到了,还支持objectreference

7.进程本地化,代码和数据在同一个进程中

SparkDriver上,对Application的每一个stagetask,进行分配之前,都会计算出每个task要计算的是哪个分片数据,RDD的某个partitionSparktask分配算法,优先,会希望每个task正好分配到它要计算的数据所在的节点,这样的话,就不用在网络间传输数据;但是呢,通常来说,有时,事与愿违,可能task没有机会分配到它的数据所在的节点,为什么呢,可能那个节点的计算资源和计算能力都满了;所以呢,这种时候,通常来说,Spark会等待一段时间,默认情况下是3s钟(不是绝对的,还有很多种情况,对不同的本地化级别,都会去等待),到最后,实在是等待不了了,就会选择一个比较差的本地化级别,比如说,将task分配到靠它要计算的数据所在节点,比较近的一个节点,然后进行计算。

所以可以适当调节等待时间。

7.1.如何判断要调节

观察日志,spark作业的运行日志,推荐大家在测试的时候,先用client模式,在本地就直接可以看到比较全的日志。日志里面会显示,starting task。。。,PROCESS LOCAL、NODE LOCAL。观察大部分task的数据本地化级别,如果大多都是PROCESS_LOCAL,那就不用调节了,如果是发现,好多的级别都是NODE_LOCAL、ANY,那么最好就去调节一下数据本地化的等待时长,调节完,应该是要反复调节,每次调节完以后,再来运行,观察日志,看看大部分的task的本地化级别有没有提升;看看,整个spark作业的运行时间有没有缩短

7.2.怎么调节

spark.locality.wait,默认是3s6s10s

默认情况下,下面3个的等待时长,都是跟上面那个是一样的,都是3s

spark.locality.wait.process

spark.locality.wait.node

spark.locality.wait.rack

new SparkConf()  .set("spark.locality.wait", "10")

8.降低cache操作的内存占比

spark中,堆内存又被划分成了两块儿,一块儿是专门用来给RDDcachepersist操作进行RDD数据缓存用的;另外一块儿,就是我们刚才所说的,用来给spark算子函数的运行使用的,存放函数中自己创建的对象。默认情况下,给RDD cache操作的内存占比,是0.660%的内存都给了cache操作了。但是问题是,如果某些情况下,cache不是那么的紧张,问题在于task算子函数中创建的对象过多,然后内存又不太大,导致了频繁的minor gc,甚至频繁full gc,导致spark频繁的停止工作。性能影响会很大。

在yarn去运行的话,那么就通过yarn的界面,去查看你的spark作业的运行统计,很简单,大家一层一层点击进去就好。可以看到每个stage的运行情况,包括每个task的运行时间、gc时间等等。如果发现gc太频繁,时间太长。此时就可以适当调价这个比例。

降低cache操作的内存占比,大不了用persist操作,选择将一部分缓存的RDD数据写入磁盘,或者序列化方式,配合Kryo序列化类,减少RDD缓存的内存占用;降低cache操作内存占比;对应的,算子函数的内存占比就提升了。这个时候,可能,就可以减少minor gc的频率,同时减少full gc的频率。对性能的提升是有一定的帮助的。

一句话,让task执行算子函数时,有更多的内存可以使用。spark.storage.memoryFraction0.6 -> 0.5 -> 0.4 -> 0.2

9.修改executor堆外内存

9.1. 有时候,如果你的spark作业处理的数据量特别特别大,几亿数据量;然后spark作业一运行,时不时的报错,shuffle file cannot findexecutortask lostout of memory(内存溢出);

可能是说executor的堆外内存不太够用,导致executor在运行的过程中,可能会内存溢出;然后可能导致后续的stagetask在运行的时候,可能要从一些executor中去拉取shuffle map output文件,但是executor可能已经挂掉了,关联的block manager也没有了;所以可能会报shuffle output file not foundresubmitting taskexecutor lostspark作业彻底崩溃

上述情况下,就可以去考虑调节一下executor的堆外内存。也许就可以避免报错;此外,有时,堆外内存调节的比较大的时候,对于性能来说,也会带来一定的提升。

9.2.如何调节

--conf spark.yarn.executor.memoryOverhead=2048

spark-submit脚本里面,去用--conf的方式,去添加配置;一定要注意!!!切记,不是在你的spark作业代码中,用new SparkConf().set()这种方式去设置,不要这样去设置,是没有用的!一定要在spark-submit脚本中去设置。

spark.yarn.executor.memoryOverhead(看名字,顾名思义,针对的是基于yarn的提交模式)

默认情况下,这个堆外内存上限大概是300M;后来我们通常项目中,真正处理大数据的时候,这里都会出现问题,导致spark作业反复崩溃,无法运行;此时就会去调节这个参数,到至少1G1024M),甚至说2G4G通常这个参数调节上去以后,就会避免掉某些JVM OOM的异常问题,同时呢,会让整体spark作业的性能,得到较大的提升。

9.3.远程拉取数据建立网络异常

当上面的blockmanager本地没有数据就会尝试建立远程的网络连接,并且去拉取数据,此时如果对应的blockmanager由于oom崩溃,就会连接没有响应,无法建立网络连接;会卡住;okspark默认的网络连接的超时时长,是60s;如果卡住60s都无法建立连接的话,那么就宣告失败了。

碰到一种情况,偶尔,偶尔,偶尔!!!没有规律!!!某某file。一串file iduuiddsfsfd-2342vs--sdf--sdfsd)。not foundfile lost

这种情况下,很有可能是有那份数据的executorjvm gc。所以拉取数据的时候,建立不了连接。然后超过默认60s以后,直接宣告失败。

报错几次,几次都拉取不到数据的话,可能会导致spark作业的崩溃。也可能会导致DAGScheduler,反复提交几次stageTaskScheduler,反复提交几次task。大大延长我们的spark作业的运行时间。

可以考虑调节连接的超时时长。

--conf spark.core.connection.ack.wait.timeout=300

spark-submit脚本,切记,不是在new SparkConf().set()这种方式来设置的。

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值