Spark调优秘诀

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.提高并行度

  • 在实际使用Spark集群的时候,很多时候对于集群的资源并不是一定会被充分利用到,这是由于task和cpu核的协调不好导致的。要想合理的“榨干”集群的资源和性能,可以合理的设置Spark应用程序运行的并行度,来充分地利用集群的资源,这样才能充分的提高Spark应用程序的性能。
  • Spark的数据源有两种,一种是外部的,比如HDFS等分布式文件系统,或者通过现有的数组等数据结构序列化而成;一种是通过已有的RDD转换而来的。这里以Spark读取HDFS的数据为例子。Spark会根据读取HDFS的时候把每个block划分为一个Partition,其实也是按照这个来自动设置并行度的。对于reduceByKey等会发生shuffle的算子操作,会使用并行度最大的父RDD的并行度作为Spark应用的并行度。
  • 通过上面的分析,我们可以手动设置并行度,在读取HDFS或者并行化数据的时候调用textFile()和parallelize()等方法的时候,通过第二个参数来设置并行度。也可以使用spark.default.parallelism参数,来设置统一的并行度。根据Spark官方的推荐,最优的方案是给集群中的每个cpu core设置2~3个task,也就是task的数量是cpu核的2~3倍。

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和MapReduce是如今两个最流行的大数据框架,它们的原理都是计算移动,而数据不移动,计算找数据。这样做的创新性是避免了大量数据的网络传输造成网络IO和内存的消耗。因此引出一个叫“数据本地化”的概念。
  • 数据本地化对于Spark Job性能有着巨大的影响。如果数据以及要计算它的代码是在同一个节点,性能会非常高。但是,如果数据和计算它的代码是位于不同的节点,那么其中之一必须到另外一方的机器上。通常来说,移动代码到其他节点,会比移动数据到代码所在的节点上去,速度要快得多,因为代码比较小。Spark也正是基于这个数据本地化的原则来构建task调度算法的。
  • 数据本地化,指的是,数据离计算它的代码有多近。基于数据距离代码的距离,有几种数据本地化级别:
    • 1、PROCESS_LOCAL:数据和计算它的代码在同一个JVM进程中。
    • 2、NODE_LOCAL:数据和计算它的代码在一个节点上,但是不在一个进程中,比如在不同的executor进程中,或者是数据在HDFS文件的block中。
    • 3、NO_PREF:数据从哪里过来,性能都是一样的。
    • 4、RACK_LOCAL:数据和计算它的代码在一个机架上。
    • 5、ANY:数据可能在任意地方,比如其他网络环境内,或者其他机架上。

Spark数据本地化的特点

  • Spark倾向于使用最好的本地化级别来调度task,但并不是每次都会使用最好的本地化数据的。在实际中,如果没有任何未处理的数据在空闲的executor上,Spark会放低本地化级别。这时有两个选择:第一,driver等待,直到executor上的cpu释放出来,就分配task等资源给这个executor;第二,立即在任意一个executor上启动一个task。
  • Spark会默认等待一段时间(这个事件可以通过参数来设置),来期望在task要处理的数据所在的节点上的executor空闲出一个cpu,从而为其分配task鞥资源。但只要超过了时间,Spark就会将task分配到其他任意一个空闲的executor上。
  • 可以设置参数,spark.locality系列参数,来调节Spark等待task可以进行数据本地化的时间。spark.locality.wait(3000毫秒)、spark.locality.wait.node、spark.locality.wait.process、spark.locality.wait.rack
  • 针对以上的分析,我们可以这样调优,增大查找本地化数据的超时时间和重试次数,因为时间更长更利于查找本地化数据的节点的executor,重试次数越多,更多机会尝试查找本地化数据的节点的executor。
  • 调优方式,主要是spark.locality.wait(3000毫秒)、spark.locality.wait.node、spark.locality.wait.process、spark.locality.wait.rack这些参数,具体的根据实际的业务需求来控制参数就OK了。

10.reduceByKey和groupByKey的选择

以下两种方式是等价的,但是实现的原理却不相同。reduceByKey,因为它会在map端,先进行本地combine,可以大大减少要传输到reduce端的数据量,减小网络传输的开销。而groupByKey算子却不会这样优化。所以只有在reduceByKey处理不了时,才用groupByKey().map()来替代。

你可能感兴趣的:(spark)