spark调优秘诀
1.诊断内存的消耗
以上就是Spark应用程序针对开发语言的特性所占用的内存大小,要通过什么办法来查看和确定消耗内存大小呢?
可以自行设置Rdd的并行度,有两种方式:第一,在parallelize(),textFile()等外部数据源方法中传入第二个参数,设置rdd的task/partition的数量;第二个用sparkconf.set()设置参数(spark.defult.parallelism),统一设置整个spark Application 所有Rdd的partition数量。
调用Rdd.cache(),将rdd cache到内存中,方便直接冲log信息中看出每个partition消耗的内存。
找出drive进程的log信息,查看每个 partition的数据的内存消耗 将这个消耗信息乘以part的数量,记得出Rdd的内存占用量。
2.高性能序列化类库
分布式程序能够工作,首先需要各节点之间的通信,而通信最重要的是序列化和反序列化。在spark中。默认使用的是java自带的序列化机制。它是基于objectInputstream 和ou.因为它是java自带的考虑表较多,序列化后占用的内存还是较大。所以可以采用另一种序列化机制,kryo序列化机制。他比java序列化更快,序列化后的内存更小,虽然实现了Seriralizable接口,但是不一定能够进行序列化;此外,如果要得到最佳的性能,需要在Spark应用程序中,对所有 需要序列化的类型都进行注册。
总结,需要用到Kryo序列化机制的场景,算子内部使用了外部的大对象或者大数据结构。那么可以切换到Kryo序列化,序列化速度更快,和获得更小的序列化数据,减少内存的消耗。
3.优化数据结构
不管是java还是scala,对他们的数据结构进行优化。
1.能使用数组或字符串就不要用集合类
2、避免使用多层嵌套的对象结构
3.能用int就不用String
总结,在写Spark程序的时候,要牢牢记住,尽量压榨因语言带来的内存开销,达到节约内存的目的。
4.对多次使用的RDD进行持久化或Checkpoint
1. 同一份数据应该只产生一个rdd,避免重复创建,减少作业的性能开销
2.尽可能的复用同一个RDD,这样就可以减少rdd的数量。减少算子的执行次数
3.对于多次使用的rdd进行持久化,这个时候spark就会根据rdd中的数据保存到内存或者磁盘中,以后每次对这个RDD进行算子操作时,都会直接从内存或磁盘中提取持久化的RDD数据,然后执行算子,而不会从源头处重新计算一遍这个RDD,再执行算子操作。
5.使用序列化的持久化级别
rdd的数据是持久化到内存,或者磁盘中的。再生产环境中机器不单单是跑这么一个Spark应用的,还需要留些内存供其他应用使用。这种情况下,可以使用序列化的持久化级别
将数据序列化之后,再持久化,可以大大减小对内存的消耗。此外,数据量小了之后,如果要写入磁盘,磁盘io性能消耗也比较小。
RDD持久化序列化后,RDD的每个partition的数据,都是序列化为一个巨大的字节数组。这样,对于内存的消耗就小了。但是唯一的缺点是获取RDD数据时,需要对其进行反序列化,会增大其性能开销。这种情况下可以使用第二点的Kryo序列化机制配合,提高序列化的效率
6.Java虚拟机垃圾回收调优
7.提高并行度
8.广播共享数据
为啥会有广播变量?
RDD实质是弹性分布式数据集,在每一个节点中的每一个task操作的只是Rdd的一部分数据,如果Rdd算子操作使用到了算子函数外部的一份大数据的时候,实际上是spark应用程序把数据文件通过Drive发送给每一个节点的task。比如说,用到的外部数据是map 1m。有1000task,这些task里面都用到了占用1内存的map.首先map会被拷贝1000份副本,通过网络传输到各个task中,给task使用,总共是1G的数据,会有很大的网络传输开销。(消耗掉你的spark作业运行的总时间的一小部分。)
map副本传到各个task上之后。也要消耗掉1G的内存,这些不必要的内存的消耗和占用,就会导致,在进行RDD持久话到内存的时候,可能会无法再内存中放下,就要写入到磁盘,最后会导致后续的操作在磁盘io上消耗内存,
task在创建对象的时候,也许会发现堆内存放不下所有的对象,就会导致频繁的垃圾回收器的回收,gc的时候,一定会导致工作线程停止,也就是会导致spark暂停工作那么一点的时间。频发gc的话对spark的运行速度回有很大的影响。
怎么解决哩
这时候,就能体现出广播变量的优点了,广播变量,并不是每个task一份数据副本,而是每个executor才一份副本,这样的话,就可以大大的减少副本的量。如果你的算子函数中,使用到了特别大的数据,那么,这个时候,推荐将该数据进行广播。这样的话,就不至于将一个大数据拷贝到每一个task上去。而是给每个节点拷贝一份,然后节点上的task共享该数据。
这样的话,就可以减少大数据在节点上的内存消耗。并且可以减少数据到节点的网络传输消耗。
广播变量,初始的时候,就在Drvier上有一份副本。
task在运行的时候,想要使用广播变量中的数据,此时首先会在自己本地的Executor对应的BlockManager中,尝试获取变量副本;如果本地没有,那么就从Driver远程拉取变量副本,并保存在本地的BlockManager中;此后这个executor上的task,都会直接使用本地的BlockManager中的副本。
executor的BlockManager除了从driver上拉取,也可能从其他节点的BlockManager上拉取变量副本,举例越近越好。
举例来说,(虽然是举例,但是基本都是用我们实际在企业中用的生产环境中的配置和经验来说明的)。50个executor,1000个task。一个map,10M。
默认情况下,1000个task,1000份副本。10G的数据,网络传输,在集群中,耗费10G的内存资源。
如果使用了广播变量。50个execurtor,50个副本。500M的数据,网络传输,而且不一定都是从Driver传输到每个节点,还可能是就近从最近的节点的executor的bockmanager上拉取变量副本,网络传输速度大大增加;500M的内存消耗。
10000M,500M,20倍。20倍~以上的网络传输性能消耗的降低;20倍的内存消耗的减少。
对性能的提升和影响,还是很客观的。
虽然说,不一定会对性能产生决定性的作用。比如运行30分钟的spark作业,可能做了广播变量以后,速度快了2分钟,或者5分钟。但是一点一滴的调优,积少成多。最后还是会有效果的。
没有经过任何调优手段的spark作业,16个小时;三板斧下来,就可以到5个小时;然后非常重要的一个调优,影响特别大,shuffle调优,2~3个小时;应用了10个以上的性能调优的技术点,JVM+广播,30分钟。16小时~30分钟。
9.数据本地化
Spark数据本地化的基本原理
Spark数据本地化的特点
10.reduceByKey和groupByKey的选择
以下两种方式是等价的,但是实现的原理却不相同。reduceByKey,因为它会在map端,先进行本地combine,可以大大减少要传输到reduce端的数据量,减小网络传输的开销。而groupByKey算子却不会这样优化。所以只有在reduceByKey处理不了时,才用groupByKey().map()来替代。