1. 分配更多的资源
首选,一定范围内,增加资源与性能的提升成正比,在资源最大化后考虑后面的调优
1.1 分配哪些资源
executor‐memory、executor‐cores、driver‐memory
1.2 在哪里可以设置这些资源
在spark-submit shell 脚本进行集群资源的分配
1.3资源最大化如何实现
第一种情况:标准模式 standalone模式下
比如共有20个worker节点,每个节点的配置是8g内存,10个cpu
那么实际分配资源的时候可以给20个excutor,每个excutor的内存8g,每个excutor的使用cpu为10
第二种情况:Yarn
先计算出yarn集群的所有大小,比如一共500g内存,100个cpu; 这个时候可以分配的最大资源,比如给定50个executor、每个executor的内存大小10g,每个executor使用的cpu 数为2。
2. 提高并行度
并行度:各个stage中的task的数量,也就代表了spark各个阶段stage的并行度,我们说的RDD并行计算是在各个stage中并行计算的,而stage和stage之间是串行的,也就是又先后顺序的。
并行度的提高应该结合当前资源的情况进行设置。比如,50个excutor,每个excutor内存10g,3个cpu core数,那么task应该至少为150个,少于150个就会造成资源浪费,一般每个core分配2-3个task比较合理(也就是2-3个partition)
2.1如何提高并行度
2.2.1增加task数量(sparkcore)
第一种方式
通过设置参数spark.default.parallelism
默认是没有值的,如果设置了值为10,它会在shuffle的过程才会起作用。 比如
val rdd2 = rdd1.reduceByKey(_+_) 此时rdd2的分区数就是10,rdd1的分区数不受这个参数的影响。
可以通过在构建SparkConf对象的时候设置,例如:new SparkConf().set("spark.defalut.parallelism","500")
第二种方式
通过给RDD进行重新分区间接实现增加task的操作(因为一个RDD会有多个分区,每个分区对应一个分片,每个分片会对应一个task,增加partition数量就是间接增加了task数量)(可以想象成mr中的一个文件就是一个RDD)
2.2.2提高sparksql运行的task数量
通过设置参数 spark.shuffle.partitions=500 默认是200
3. RDD的重用和持久化
rdd1-->rdd2-->rdd3-->rdd4
rdd1-->rdd2-->rdd3-->rdd5
可以把多次使用的RDD进行持久化(rdd3),避免重复计算
3.1如何进行持久化
可以使用cache或者persist方法
cache是persist中的一种,将RDD缓存到内存,参数
persist中存在多个缓存级别以及多个缓存份数(磁盘内存)
3.2rdd持久化的时可以采用序列
如果因为内存无法存储公共的rdd,导致oom,那么就可以采用将RDD序列化到内存中,序列化后会大大减少内存空间。
如果序列化后内存还是oom了,只能将RDD缓存到磁盘
序列化的缺点,获取数据时要进行反序列化,优点:可以减少占用的内存空间和提高网络传输的速率
3.3 广播变量的使用
spark中分布式执行代码需要传递到各个excutor中的task上运行。对于固定的数据,每次都要从driver端发送到各个task中去,造成大量的网络传输消耗。可以提前将这些数据广播到每个excutor中,每个excutor中的task共用一份数据。
广播变量最初的时候会在driver端保存一份副本,task在运行时,会在所在的excutor对应的blockmanager中,尝试获取广播变量,如果没有就会到driver中加载数据并存到blockmanager中
executor的BlockManager除了从driver上拉取,也可能从其他节点的BlockManager上拉取变量副本,网络距离 越近越好。
3.3.1如何使用广播变量
例如: (1) 通过sparkContext的broadcast方法把数据转换成广播变量,类型为Broadcast, val broadcastArray: Broadcast[Array[Int]] = sc.broadcast(Array(1,2,3,4,5,6)) (2) 然后executor上的BlockManager就可以拉取该广播变量的副本获取具体的数据。 获取广播变量中的值可以通过调用其value方法 val array: Array[Int] = broadcastArray.value
4.降低cache操作的内存占比
jvm内存不充足的时候,会出现的问题: (1)、频繁minor gc,也会导致频繁spark停止工作 (2)、老年代囤积大量活跃对象(短生命周期的对象),导致频繁full gc,full gc时间很长,短则数十秒,长则数分 钟,甚至数小时。可能导致spark长时间停止工作。 (3)、频繁gc会严重影响spark的性能和运行的速度。
cache操作的内存占比为堆内存的0.6 也就是百分之60,可以适当调节,降低该值, 修改spark.storage.memoryFraction参数 可以设置为0.5‐‐‐>0.4‐‐‐‐‐‐>0.3 例如: new SparkConf().set("spark.storage.memoryFraction","0.4") 把cache操作的内存占比修改为堆内存的百分之40,让堆内存可以容纳更多的对象,减少gc的频率,提高spark任务运行 的速度和性能。
5.数据倾斜调优
5.1数据倾斜发生时的现象
1.有些task很快运行完,有些要一两个小时
2.原本正常执行的spark作业,某天突然oom(内存溢出)异常,这种情况比较少见
5.2数据倾斜发生的原理
shuffle完成后会对相同的key进行拉取到某个节点上的task进行处理,如果某个key对应的数据量特别大的话,就会发生数据倾斜
5.3如何定位导致数据倾斜的代码
数据倾斜只会发生在shuffle过程中,常用的并且可能会触发shuffle操作的算子:distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、 repartition等出现数据倾斜时,可能就是你的代码中使用了这些算子中的某一个所导致的。
5.3.1某个task执行特别慢的情况
5.3.2某个task莫名其妙内存溢出的情况
数据倾斜的解决方案
解决方案一:使用Hive ETL预处理数据
解决方案二:过滤少数导致倾斜的key
解决方案三:提高shuffle操作的并行度
解决方案四:两阶段聚合(局部聚合+全局聚合)
解决方案五:将reduce join转为map join
解决方案六:采样倾斜key并分拆join操作