spark性能优化

本文探讨了Spark性能优化的多个方面,包括避免OOM的方法、硬件优化、数据倾斜处理、网络优化、序列化策略、数据结构优化、内存诊断、持久化与检查点以及JVM性能调优。建议通过合理设置executor数量、内存分配、partition数量,减少数据倾斜,使用Kryo序列化,优化数据结构,以及监控和调整JVM的GC参数来提升Spark作业的执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

优化思路:
我认为性能优化首先的前提是程序没有错,尤其是没有oom的前提下,才能谈性能优化,从而让程序运行的快速、高效。那么现一、在谈谈怎么避免oom的几个方案:
解决的核心特点就是1.增大内存 2.减少任务内存的使用率
对于增大内存方案:
1减少worker上executor的数量 (这样每个executor占用的内存就大了)
2.设置spark.stage.memoryFraction
对于减少任务对内存的使用率方案:
1.减少executor上的core数量 (并行的任务少了,内存的占用就少了)
2.

二、优化
硬件
1.处理的spark job 的过程如果出现特别多的小文件,这个时候就可以用colesce来减少partition的数量,进而减少并行运算的task的数量来减少任务的开辟,也就是减少频繁的切换任务的执行(cpu),从而提升硬件的使用率

2.partition的设置很重要:partition过少,每个partition数据过大导致oom以及频繁的gc
partition过多,每个partition数据过小,导致执行率底下(频繁切换task)

3处理spark job的时候如果发现某些任务task运行的特别慢,方法一针对性的处理(数据倾斜),方法二考虑增加任务的并行度,减少每个partition的数据量来提升执行效率。可以尝试打开spark.speculation(任务推测 就是把相同任务在别的机子运行,当有一个完成,会杀掉其他相同的任务,以第一个完成的任务为时间) 方法三增加executor的个数提高并行度,这样每个executor的分配的资源就少了,可以提高硬件整体使用效率(方法三的这个方法没有搞明白,资源少了之后慢的task不是就更慢了吗? 我感觉是不对的)

4.gc的时间一般不要超过cpu时间的2%

三、数据倾斜
1.定义更加合理的key(或者说字典一repatition)
2.可以考虑使用ByteBuffer来存储Block。最大存储为2G,如果超过汇报异常
3.

四、网络
1.可以考虑将shuffle的数据放到tackyon上,减少网络的shuffle
2.优先考虑Netty的方式进行网络通信
3.广播:例如在进行join操作的时候采用broadcast可以达到完全数据本地性的情况进行本地操作
4.mapPartition中的函数会直接作用于整个Partition(一次!!!!)
5.最优先考虑process local(spark默认情况就是这样的),所以更应该使用tackyon

五、序列化
1.原因:
a)减少内存空间的占用(同样会减少gc的压力,最大化的避免full gc 的发生,因为full gc的发生,则整个Task处于停止的状态!!!)
b)减少磁盘io的压力
c)减少网络io的压力
2.需要使用序列化情况是io和网络通信的时候必须需要序列化和反序列化,表现为如下情况:
a)persist的时候需要,例如cache的时候只能占用jvm的0.6内存空间,当用序列化一定量的内存能存储更多的对象,减少了io压力
b)编程的时候:使用算子的函数操作使用了外部数据就必须使用序列化和反序列化
sparkConf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”);
sparkConf.registerKryoClasses(Array(classOf[InPutFile3],classOf[OutPutFile3]))
val input = new InPutFile3()
rdd.map(x=>input.add(x))
3.强烈建议使用kryo序列化器,进行序列化和反序列化,spark默认使用的不是kryo,而是java自带的序列化器objectInputStream和ObjectOuputStream(主要是考虑方便性和通用性),在默认情况下如果自定义了rdd中数据元素的类型则必须实现seralizable接口,当然你也可以实现自己的序列化接口Externalizable来实现更加高效的java序列化算法,采用默认的序列化后的数据占用大量的内存或者磁盘或者大量的消耗网络,并且在序列化和反序列化的时候比较消耗cpu
4.强烈土建大家采用kryo序列化机制,spark下使用kryo序列化机制会比java默认的序列化机制更加节省空间(节省近10倍的空间)以及更少的消耗cpu,个人强烈推荐大家在一切情况下尽可能的使用kryo序列化机制!
5spark中scala常用的类型自动的通过ALLScalaRegisty注册给了Kryo进行序列化管理
6.kryo在序列化的时候缓存空间默认大小为2MB,可以根据具体的业务模型调整大小,(spark.kryoserializer.buffer)
7.在使用kryo强烈建议注册的时候写完整的包名和类名,否则的话每次序列化的时候都会保存一份整个包名和类名的完整信息,这就会造成不必要的消耗内存空间(在数据量越大,表现越明显)

六.数据结构优化
1,Java的对象:对象头是16个字节(例如指向对象的指针等元数据信息),如果对象只有一个int的property,则此时会占据20字节,也就是说对象的元数据占用了大部分的空间,所有在封装数据的时候尽量不要使用对象!例如说使用JSON格式来封装数据;
2,Java中的基本的数据类型会自动的封箱操作,例如int会自动变成Integer,这会额外增加对象头的空间占用;
3,Java的String在实际占用内存方面要额外使用40个字节(String的内部使用char[]来保存字符序列),另外需要注意的是String每个字符是2字节(UTF-16),如果内部有5个字符的话,实际上会占用50个字节;
4,Java中的集合List,HashMap等等其内部一般使用链表,具体的每个数据使用Entry等,这些也非常消耗内存;
5,优先使用原生数组,尽可能不要使用ArrayLiast、HashMap、LinkedList等数据结构,例如说List list = new ArrayList()需要考虑替换为int[] array = new int[];
6,优先使用String(推荐使用JSON),而不是采用HashMap、List等来封装数据,例如Map workers = new HashMap(),建议使用JSON字符串或者自己构建的String字符串对象,例如“id:name,salary||id:name,salary||id:name,salary”;

七,内存诊断
八.persist chackpoint
1,当反复使用某个(些)RDD的时候强烈建议使用persist来对数据进行缓存(MEMORY_AND_DISK);
2,如果某个RDD就算特别耗时或者经历了很多步骤的计算,如果数据丢失则重新计算的代价特别大,此时考虑使用checkpoint,因为checkpoint是把数据写入HDFS,天然具有高可靠性;

九.jvm性能调优
1、好消息是Spark的钨丝计划是用来专门解决JVM性能问题的,不好的消息至少在Spark2.0以前钨丝计划功能不稳定且不完善,且只能在特定的情况下发生作用,也就是说包括Spark1.6.0在内的Spark及其以前的版本我们大多数情况下没有使用钨丝计划的功能,所以此时就必须关注JVM性能调优;
2、JVM性能调优的关键是调优GC!!!为什么GC如此重要?主要是因为Spark热衷于RDD的持久化!!!GC本身的性能开销是和数据量成正比的;
3、初步可以考虑的是尽量多的使用array和String,并且在序列化机制方面尽可能的采用Kryo,让每个partition都成为字节数组;
4、监控GC的方式有两种:
1)配置
spark.executor.extraJavaOptions
-verbose:gc -XX:+PrintGCDetails -XX:+ PrintGCDateTimeSatmps

2)sparkUI
5、Spark默认情况下使用60%的空间来进行cache缓存RDD的内容,也就是说Task在执行的时候,只能使用剩下的40% 的空间,如果空间不够用,就会触发(频繁的)GC
可以设置spark.memory.fraction参数来调整空间的使用,例如降低Cache的空间,让Task使用更多的空间来创建对象和完成计算;
再一次,强烈建议进行从RDD进行cache的时候使用Kryo进行序列化,从而给Task可以分配更大的空间来顺利完成计算(避免频繁的GC);
6、因为在老年带空间满的时候会发生FULL GC操作,而老年带空间中基本都是活的比较久的对象(经历数次GC依旧存在),此时会停下所有的程序线程,进入FULL GC,对OLD区中的对象进行整理,严重影响性能,此时可以考虑:
1)设置spark.memory.fraction参数来进行调整空间的使用来给年轻代更多的空间用于存放短时间存活的对象;
2)-Xmn调整Eden区域:对RDD中操作的对象和数据进行大小评估,如果在HDFS上解压后一般体积可能会变成原有体积的3倍左右,根据数据的大小来设置Eden,如果有10个Task,每个Task处理的HDFS上的数据是128MB,则需要设置-Xmn为10*128*3*4/3的大小;
3)-XX:SupervisorRatio;
4)-XX:NewRatio;
SupervisorRatio、NewRatio 正常情况下不用随便调,前提是对JVM非常了解的前提下
但是数据级别到PB级别之后,就完全不是这么一回事了,就要去研究JVM了!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值