Spark优化
主要分为两个方面的优化,一是代码逻辑的优化,二是资源配置的优化
1.代码逻辑
1.1.RDD优化
RDD优化主要也有两个方面的考虑,一是RDD的复用,二是RDD的持久化。那么主要针对RDD的持久化进行说明。在Spark中多次对同一个RDD执行算子时,每次都会对这个RDD的父RDD重新计算一次,所以要避免这种重复计算的资源浪费,那么就需要对RDD进行持久化。
Memory_Only | 内存 |
Memory_Only_2 | 内存+数据备份 |
Memory_Only_Ser | 内存+序列化 |
Memory_And_Disk | 内存+磁盘 |
Memory_And_Disk_2 | 内存+磁盘+备份 |
Memory_And_Disk_Ser | 内存+磁盘+序列化 |
Disk_Only | 磁盘 |
1.2.并行度优化
基本概念:
- CPU核数
一台服务器有两颗CPU,每颗CPU是16核,一颗CPU的超线程数是2,所以:
假设我们的集群是100台服务器那么:
总核数 = 100 * 2 * 16 = 3200
总的逻辑CPU数 = 100 * 2 * 16 * 2 = 6400
- RDD分区
默认参数:spark.files.maxPartitionBytes = 128 M,就是说当一个文件大小超过128M才会拆成两个分区,而当SparkContext创建后会生成两个参数
sc.defaultParallelism = spark.default.parallelism
sc.defaultMinPartitions = min(spark.default.parallelism,2)
也就是说,我们可以根据这两个参数来推算RDD的分区数量。当读取本地文件时:默认RDD分区公式: max(本地file的分片数, sc.defaultMinPartitions)。当读取HSFS文件时:默认RDD分区公式:max(hdfs文件的block数目, sc.defaultMinPartitions)
Spark并行度:
Spark并行度就是每个Spark作业中各个Stage中所有task的数量,也就表示了Spark作业在各个阶段的并行度,需要知道的是DAGScheduler会根据Spark作业中是否存在Shuffle Dependency来划分Stage,然后为每个Stage中的Task组成TaskSet交给TaskScheduler,那么TaskScheduler会将TaskSet发送到Executor进行处理,当Executor接受到TaskSet之后进行反序列化之后把这些Task封装到TaskRunner的线程中执行。Spark并行度是由参数spark.default.parallelism所控制的,而默认的并行度分为这么几种:
- 本地模式
在local[n]模式下:spark.default.parallelism = n
- 集群
在Sandalone/Yarn模式下:spark.default.parallelism = 所有Executor数乘以每个Executor的core数,也就是所有Executor总的core数
总结:
比如:集群规模是100台,每台服务器两颗CPU,每颗CPU16核,那么总的CPUcores就是6400个,如果在计算的任何stage中使用的并行task的数量没有足够多,那么集群资源是无法被充分利用。所以为了充分利用系统资源尽可能提高。
- 参数一:num-executors
该参数用于设置Spark作业总共需要用多少个Executor来执行
- 参数二:executor-cores
该参数用于设置每个Executor进程的CPU core数量
- 参数三:spark.default.parallelism
该参数用于设置每个Stage默认的Task数量
补充:
每个几点可以启动一个或多个Executor,每个Executor由一个或多个core组成,每个core只能执行一个task,每个task执行的结果产生一个目标partition
1.3.广播变量
广播变量其实就是将ReduceJoin转化为MapJoin,这样做的好处就是将Reduce端每个Task获取一份副本,转化为一个Executor获取一份副本,直接减少了内存开销,同时也避免了数据倾斜
def broadcastVal(sc: SparkContext): Unit = {
val kv = Map(("a",1),("b",2))
val bc = sc.broadcast(kv)
val rdd = sc.textFile("D:/io/input/txt/rdd.txt").flatMap(_.split("\\s+"))
rdd.foreach{
e=>{
if (bc.value.contains(e)) {
println(e)
}
}
}
}
通常来说只会将数据进行广播,RDD并不能广播,如果想将RDD广播出去的话,可以将RDD拉回到Driver然后将RDD的数据在广播出去
1.4.Kryo序列化
从Spark 2.0.0版本开始,简单类型、简单类型数组、字符串类型的Shuffling RDDs 已经默认使用Kryo序列化方式了。或者手动指定conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
1.5.本地化等待时长
1.6.算子优化
1.6.1.mapPartitions
1.6.2.foreachPartition
1.6.3.filter&coalesce
1.6.4.repartition
1.6.5.reduceByKey
2.资源配置
2.1.最优资源配置
--num-executors | executor个数 |
--driver-memory | Driver内存 |
--executor-cores | ExecutorCPU数量 |
--executor-memory | Executor内存 |
2.2.shuffle优化
2.2.1.Map缓冲区
默认32KB,手动指定:conf.set("spark.shuffle.file.buffer", "64")
2.2.2.Reduce缓冲区
默认48M,手动指定:conf.set("spark.reducer.maxSizeInFlight", "96")
2.2.3.Reduce重试次数
默认3次,手动指定:conf.set("spark.shuffle.io.maxRetries", "6")
2.2.4.Reduce等待间隔
默认5s,手动指定:conf.set("spark.shuffle.io.retryWait", "60s")
2.2.5.SortShuffle
默认200,手动指定:conf.set("spark.shuffle.sort.bypassMergeThreshold", "400")
2.3.JVM优化
对于JVM调优,首先应该明确,major gc/minor gc,都会导致JVM的工作线程停止工作,即stop the world
Spark 1.6 之后引入的统一内存管理机制,与静态内存管理的区别在于存储内存和执行内存共享同一块空间,可以动态占用对方的空闲区域,统一内存管理的堆内内存结构
2.3.1.Cache内存占比
静态内存管理机制:默认0.6,手动指定:conf.set("spark.storage.memoryFraction", "0.4")
统一内存管理机制:无需手动调节(动态占用机制)
2.3.2.Executor对外内存
默认300M,手动指定:--conf spark.yarn.executor.memoryOverhead=2048
2.3.3.连接时长
默认60s,手动指定:--conf spark.core.connection.ack.wait.timeout=300