Spark 调优
在调优之前可以先了解下任务提交流程
https://www.cnblogs.com/frankdeng/p/9301485.html
Spark 性能优化概览:
Spark的计算本质是,分布式计算。
所以,Spark程序的性能可能因为集群中的任何因素出现瓶颈:CPU、网络带宽、或者内存。
CPU、网络带宽,是运维来维护的。
聚焦点:内存。
如果内存能够容纳下所有的数据,那就不需要调优了。
如果内存比较紧张,不足以放下所有数据(10亿量级---500G),需要对内存的使用进行性能优化。
比如:使用某些方法减少内存的消耗。
Spark性能优化,主要针对在内存的使用调优
Spark性能优化的技术:
1、使用高性能序列化类库
2、优化数据结构
3、对于多次使用的RDD进行持久化、checkpoint
4、持久化级别:MEMORY_ONLY —> MEMORY_ONLY_SER 序列化
5、Java虚拟机垃圾回收调优
6、Shuffle调优,1.x版本中,90%的性能问题,都是由于Shuffle导致的。
其他性能优化:
1、提高并行度
2、广播共享数据
等等。。。
诊断Spark内存使用
首先要看到内存使用情况,才能进行针对性的优化。
1、内存花费:
(1)每个Java对象,都有一个对象头,占用16字节,包含一些对象的元信息,比如指向他的类的指针。
如果对象本身很小,比如int,但是他的对象头比对象自己还大。
(2)Java的String对象,会比他内存的原始数据,多出40个字节。
String内部使用的char数组来保存内部的字符串序列,并且还要保存诸如输出长度之类的信息。
char使用的是UTF-16编码,每个字符会占2个字节。比如,包含10个字符的String,2*10+40=60字节
(3)Java中的集合类型,比如HashMap和LinkedList,内部使用链表数据结构。
链表中的每个数据,使用Entry对象包装。
Entry对象,不光有对象头,还有指向下一个Entry的指针,占用8字节。
(4)元素类型为原始数据类型(int),内部通常会使用原始数据类型的包装类型(Integer)来存储元素。
2、如何判断Spark程序消耗内存情况?
预估
(1)设置RDD的并行度。
两种方法创建RDD,parallelize() textFile() 在这两个方法中,传入第二个参数,设置RDD的partition数量。
在SparkConfig中设置一个参数:
spark.default.parallelism
可以统一设置这个application中所有RDD的partition数量
(2)将RDD缓存 cache()
(3)观察日志:driver日志
/usr/local/spark-2.1.0-bin-hadoop2.7/work
19/04/13 22:01:05 INFO MemoryStore: Block rdd_3_1 stored as values in memory (estimated size 26.0 MB, free 339.9 MB)
19/04/13 22:01:06 INFO MemoryStore: Block rdd_3_0 stored as values in memory (estimated size 26.7 MB, free 313.2 MB)
(4)将这个内存信息相加,就是RDD内存占用量。
一.使用高性能序列化类库
1、数据序列化概述
数据序列化,就是将对象或者数据结构,转换成特定的格式,使其可在网络中传输,或存储在内存或文件中。
反序列化,是相反的操作,将对象从序列化数据中还原出来。
序列化后的数据格式,可以是二进制,xml,Json等任何格式。
对象、数据序列化的重点在于数据的交换与传输。
在任何分布式系统中,序列化都是扮演着一个重要的角色。
如果使用的序列化技术,操作很慢,或者序列化后的数据量还是很大,会让分布式系统应用程序性能下降很多。
所以,Spark性能优化的第一步,就是进行序列化的性能优化。
Spark自身默认会在一些地方对数据进行序列化,比如Shuffle。另外,我们使用了外部数据(自定义类型),也要让其课序列化。
Spark本身对序列化的便捷性和性能进行了取舍
默认情况下:Spark倾向于序列化的便捷性,使用了Java自身提供的序列化机制,很方便使用。
但是,Java序列化机制性能不高,序列化速度慢,序列化后数据较大,比较占用内存空间。
2、kryo
Spark支持使用kryo类库来进行序列化。
速度快,占用空间更小,比Java序列化数据占用空间小10倍。
3、如何使用kryo序列化机制
(1)设置Spark conf
bin/spark-submit will also read configuration options from conf/spark-defaults.conf,
in which each line consists of a key and a value separated by whitespace. For example:
spark.master spark://5.6.7.8: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类库的优化
(1)优化缓存大小
如果注册的自定义类型,本身特别大(100个字段),会导致要序列化的对象太大。
此时需要对kyro本身进行优化。因为kryo内部的缓存,可能不能存放这么大的class对象。
spark.kryoserializer.buffer.max 设置这个参数,将其调大。
(2)预先注册自定义类型
虽然不注册自定义类型,kryo也可以正常工作,但会保存一份他的全限定类名,耗费内存。
推荐预先注册要序列化的自定义类型。
二.优化数据结构
1、概述
要减少内存的消耗,除了使用高效的序列化类库外,还要优化数据结构。
避免Java语法特性中所导致的额外内存开销。
核心:优化算子函数内部使用到的局部数据或算子函数外部的数据。
目的:减少对内存的消耗和占用。
2、如何做?
(1)优先使用数组以及字符串,而不是集合类。即:优先使用Array,而不是ArrayList、LinkedList、HashMap
使用int[] 会比List<Integer> 节省内存
(2)将对象转换成字符串。
企业中,将HashMap、List这种数据,统一用String拼接成特殊格式的字符串
Map<Integer,Person> persons = new HashMap<Integer,Person>()
可以优化为:
"id:name,address"
String persons = "1:Andy,Beijing|2:Tom,Tianjin...."
3)避免使用多层嵌套对象结构
举例:
下面的例子不好,因为Teacher类的内部又嵌套了大量的小的Student对象
public class Teacher{ private .....; privage List<Student> students = new ArrayList()}
解决:转换成字符串进行处理。
{"teacherId": 1, "students":[{"stuId":1.....},{}]}
(4)对于能够避免的场景,尽量使用int代替String
虽然String比List效率高,但int类型占用更少内存
比如:数据库主键,id,推荐使用自增的id,而不是uuid
三.rdd.cache checkpoint
例如:spark sql 将表缓存
四.持久化级别:MEMORY_ONLY —> MEMORY_ONLY_SER 序列化
def persist(newLevel: StorageLevel): this.type = {
// TODO: Handle changes of StorageLevel
if (storageLevel != StorageLevel.NONE && newLevel != storageLevel) {
throw new UnsupportedOperationException(
"Cannot change storage level of an RDD after it was already assigned a level")
}
五.Java虚拟机的调优
1、概述
如果在持久化RDD的时候,持久化了大量的数据,那么Java虚拟机的垃圾回收就可能成为一个瓶颈
Java虚拟机会定期进行垃圾回收,此时会追踪所有Java对象,并且在垃圾回收时,找到那些已经不再使用的对象。
清理旧对象,给新对象腾出空间。
垃圾回收的性能开销,是与内存中的对象数量成正比。
在做Java虚拟机调优之前,必须先做好上面的调优工作,这样才有意义。
必须注意顺序
2、Spark GC原理
3、监测垃圾回收
我们可以进行监测,比如多久进行一次垃圾回收以及耗费的时间等等。
spark-submit脚本中,添加一个配置
--conf "spark.executor.extraJavaOptions=-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimesStamps"
注意:这个是输出到worker日志中,而不是driver日志。
/usr/local/spark-2.1.0-bin-hadoop2.7/logs worker日志
/usr/local/spark-2.1.0-bin-hadoop2.7/work driver日志
4、优化Executor内存比例
目的:减少GC次数。
对于GC调优来说,最重要的就是调节,RDD的缓存占用的内存空间 与 算子执行时创建对象所占用的内存空间 的比例
对于默认情况,Spark使用每个Executor 60% 的内存空间来缓存RDD,在task运行期间所创建的对象,只有40%内存空间来存放。
使用:conf.set("spark.storage.memoryFraction",0.5)
5、Java GC 调优
以上优化是针对GC的启动次数,而对于JAVA GC 调优是针对GC启动后的优化
六.shuffle
https://www.cnblogs.com/hd-zg/p/6089230.html
1、优化前
2、优化后
spark.shuffle.file.buffer
默认值:32k
**参数说明:**该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
spark.reducer.maxSizeInFlight
默认值:48m
参数说明:该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
spark.shuffle.io.maxRetries
默认值:3
参数说明:shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。
调优建议:对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿~上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。
spark.shuffle.io.retryWait
默认值:5s
参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。
调优建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。
spark.shuffle.memoryFraction
默认值:0.2
参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。
调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。
spark.shuffle.manager
默认值:sort
参数说明:该参数用于设置ShuffleManager的类型。Spark 1.5以后,有三个可选项:hash、sort和tungsten-sort。HashShuffleManager是Spark 1.2以前的默认选项,但是Spark 1.2以及之后的版本默认都是SortShuffleManager了。tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。
调优建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。
spark.shuffle.sort.bypassMergeThreshold
默认值:200
参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。
调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shuffle read task的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shuffle write性能有待提高。
spark.shuffle.consolidateFiles
默认值:false
参数说明:如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shuffle write的输出文件,对于shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。
调优建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。
七.其他调优
1、提高并行度
2、广播共享数据
使用广播变量调优:https://blog.youkuaiyun.com/weixin_41804049/article/details/79903472