Spark性能优化
主要针对内存的使用调优
Spark性能优化的技术
- 使用高性能序列化类库
- 优化数据结构
- 对于多次使用的RDD进行持久化、checkpoint
- 持久化级别:MEMORY_ONLY --> MEMORY_ONLY_SER序列化
- Java虚拟机垃圾回收调优
- Shuffle调优
一·、判断Spark内存使用
首先要看到内存使用情况,才能进行针对性的优化
(1)内存花费
- 每个Java对象,都有一个对象头,占用16字节,包含一些对象的元信息,比如指向它的类的指针。如果对象本身很小,比如int,但是他的对象头比对象自己还大
- Java的String对象,会比它内存的原始数据,多出40个字节。String内部使用的char数组来保存内部的字符串序列,并且还要保存诸如输出长度之类的信息。char使用的是UTF-16编码,每个字符会占2个字节。比如,包含10个字符的String,2*10+30=60字节
- Java中的集合类型,比如HashMap和LinkedList,内部使用链表数据结构。链表中的每个数据,使用Entry对象包装。Entry对象,不光有对象头,还有只想下一个Entry的指针,占用8字节
- 元素类型为原始数据类型(int),内部通常会使用原始数据类型的包装类型(Integer)来存储元素
- (2)判断内存消耗情况
- 设置RDD的并行度(parellelize和textFile两种方法创建RDD,在这两个方法中,传入第二个参数,设置RDD的partition数量。在SparkConfig中设置一个参数:spark.default.parallelism可以统一设置这个application中所有RDD的partition数量)
- 将RDD缓存
- 观察日志:driver日志(在Spark的work文件夹下)
- 将这个MemoryStore内存信息相加,就是RDD内存
二·、使用高性能序列化类库
- 1、数据序列化
数据序列化,就是将对象或者数据结构,转换成特定的格式,使其可在网络中传输,或存储在内存或文件中
反序列化,就是相反的操作,将对象从序列化数据中还原出来
序列化后的数据格式,可以是二进制,xml,json等任何格式
对象、数据序列化的重点在于数据的交换与传输
在任何分布式系统中,序列化都是扮演着一个重要的角色
如果使用的序列化技术,操作很慢,或者序列化后的数据量还是很大、会让分布式系统应用程序性能下降很多,所以Spark性能优化的第一步,就是进行序列化的性能优化
Spark自身默认会在一些地方对数据进行序列化,比如Shuffle。另外,我们使用了外部数据(自定义类型),也要让其可序列化
默认情况下:Spark倾向于序列化的便捷性,使用了Java自身提供的序列化机制,很方便使用。但是,Java序列化机制性能不高,序列化速度慢,序列化数据较大,比较占用内存空间
- 2、kryo
Spark支持使用kryo类库来进行序列化
速度快,占用空间更小,比Java序列化数据占用空间小10倍
- 3、使用kryo序列化机制
(1)设置Spark conf
spark.master spark://6.7.8.9:7077
spark.executor.memory 4g
spark.eventLog.enabled true
spark.serializer org.apache.spark.serializer.KryoSerializer
(2)使用kryo,需要序列化的类,提前注册,以获得高性能
conf.registerKryoClasses(Array(classOf[Count],...))
- 4、kryo类库的优化
- 优化缓存大小(如果注册的自定义类型,本身特别大(100个字段),会导致要序列化的对象太大。此时需要对kyro本身进行优化。因为kryo内部的缓存,可能不能存放这么大的class对象。设置spark.kryoserializer.buffer.max参数,将其调大)
- 预先注册自定义类型(虽然不注册自定义类型,kryo也可以正常工作,但会保存一份他的全限定类名,耗费内存。推荐预先注册要序列化的自定义类型)
三、优化数据结构
概述
要减少内存的消耗,除了使用高效的序列化类库外,还要优化数据结构,避免Java语法特性中所导致的额外内存开销
核心:优化算子函数内部使用到的局部数据或算子函数外部的数据
目的:减少对内存的消耗和占用
做法
- 优先使用数组以及字符串,而不是集合类(优先使用Array,而不是ArrayList、LinkedList、HashMap),使用int[]会比List节省内存
- 将对象转换成字符串
- 避免使用多层嵌套对象结构
- 对于能够避免的场景,尽量使用int代替String
四、Java虚拟机的调优
一·、概述
如果在持久化RDD的时候,持久化了大量的数据,那么Java虚拟机的垃圾回收就可能成为一个瓶颈
Java虚拟机会定期进行垃圾回收,此时会追踪所有Java对象,并且在垃圾回收时,找到那些已经不再使用的对象,清理旧对象,给新对象腾出空间
垃圾回收的性能开销,与内存中的对象数量成正比
做Java虚拟机调优之前,需做好前面的调优工作,才有意义
二、Spark GC原理
三、监测垃圾回收
监测多久进行一次垃圾回收一级耗费的时间等等
spark-submit脚本中,添加--conf "spark.executor.extraJavaOptions=-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimesStamps"
worker日志:spark下的logs文件夹
driver日志:spark下的worker文件夹
四、优化Executor内存比例
目的:减少GC次数
对于GC调优来说,最重要的就是调节,RDD的缓存占用空间与算子执行时创建对象所占用的内存空间的比例
对于默认情况,Spark使用每个Executor 60%的内存空间来缓存RDD,在task运行期间所创建的对象,只有40%内存空间来存放
配置:conf.set("spark.storage.memoryFraction",0.5)