Spark性能调优

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.csdn.net/weixin_41804049/article/details/79903472

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值