spark性能调优:num-executors, execuor-cores, executor-memory

execuor-cores, executor-memory二者的配比 建议 1:4

num-executors需要根据具体的核数来计算 :

   num-executors = 单机可用总核数/execuor-cores -1

spark运行原理


我们使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。根据你使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动,也可能在集群中某个工作节点上启动。Driver进程本身会根据我们设置的参数,占有一定数量的内存和CPU core。而Driver进程要做的第一件事情,就是向集群管理器(可以是Spark Standalone集群,也可以是其他的资源管理集群)申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU core。

在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了。Driver进程会将我们编写的Spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批task,然后将这些task分配到各个Executor进程中执行。task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个task处理的数据不同而已。一个stage的所有task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个stage。下一个stage的task的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据,得到我们想要的结果为止。

Spark是根据shuffle类算子来进行stage的划分。如果我们的代码中执行了某个shuffle类算子(比如reduceByKey、join等),那么就会在该算子处,划分出一个stage界限来。可以大致理解为,shuffle算子执行之前的代码会被划分为一个stage,shuffle算子执行以及之后的代码会被划分为下一个stage。因此一个stage刚开始执行的时候,它的每个task可能都会从上一个stage的task所在的节点,去通过网络传输拉取需要自己处理的所有key,然后对拉取到的所有相同的key使用我们自己编写的算子函数执行聚合操作(比如reduceByKey()算子接收的函数)。这个过程就是shuffle。

当我们在代码中执行了cache/persist等持久化操作时,根据我们选择的持久化级别的不同,每个task计算出来的数据也会保存到Executor进程的内存或者所在节点的磁盘文件中。

因此Executor的内存主要分为三块:第一块是让task执行我们自己编写的代码时使用,默认是占Executor总内存的20%;第二块是让task通过shuffle过程拉取了上一个stage的task的输出后,进行聚合等操作时使用,默认也是占Executor总内存的20%;第三块是让RDD持久化时使用,默认占Executor总内存的60%。

task的执行速度是跟每个Executor进程的CPU core数量有直接关系的。一个CPU core同一时间只能执行一个线程。而每个Executor进程上分配到的多个task,都是以每个task一条线程的方式,多线程并发运行的。如果CPU core数量比较充足,而且分配到的task数量比较合理,那么通常来说,可以比较快速和高效地执行完这些task线程。

参数调优

1.spark.hadoopRDD.ignoreEmptySplits

默认是false,如果是true,则会忽略那些空的splits,减小task的数量。

2.spark.hadoop.mapreduce.input.fileinputformat.split.minsize

是用于聚合input的小文件,用于控制每个mapTask的输入文件,防止小文件过多时候,产生太多的task。

3. spark.sql.autoBroadcastJoinThresholdspark.sql.broadcastTimeout
用于控制在 spark sql 中使用 BroadcastJoin 时候表的大小阈值,适当增大可以让一些表走 BroadcastJoin,提升性能,但是如果设置太大又会造成 driver 内存压力,而 broadcastTimeout 是用于控制 Broadcast 的 Future 的超时时间,默认是 300s,可根据需求进行调整。

4. spark.sql.files.opencostInBytes
该参数默认 4M,表示小于 4M 的小文件会合并到一个分区中,用于减小小文件,防止太多单个小文件占一个分区情况。

5. spark.sql.shuffle.partitionsspark.default.parallelism
spark.default.parallelism只有在处理RDD时有效
spark.sql.shuffle.partitions则是只对SparkSQL有效
建议设置为当前spark job的总core数量的2~3倍

举个例子:
假设我们给当前Spark job 设置总Core数为 100, 那么依据1 core 1 task,
当前spark集群中最多并行运行100task任务, 那么通过设置上述两个参数为100, 使得我们结果RDD的分区数为100, 一个分区1task 1core, 完美! 但是实际生产中会有这样的情况, 100个task中有些task的处理速度快, 有些处理慢,假设有20个task很快就处理完毕了, 此时就会出现 我们集群中有20个core处理闲置状态,不符合spark官网所说的最大化压榨集群能力.  而如果我们设置上述参数值为199, 此时的现象: 虽然集群能并行处理199个task,奈何总core只有100, 所以会出现有99个task处于等待处理的情况.处理较快的那20task闲置下来的20个core就可以接着运行99个中的20个task, 这样就最大化spark集群的计算能力

6.spark.driver.memoryexecutor memory
driver memory主要用来存储一些DAG图信息,任务调动,资源划分等信息,还有一些CollectAsList等算子生成的数据也会放到Dirver memory中executor memory是每个节点上占用的内存

7. num-executorsexecuor-cores
每个executor从YARN请求的内存 = spark-executor-memory +

8. spark.yarn.executor.memoryOverhead
spark.yarn.executor.memoryOverhead = Max(384MB, 7% of spark.executor-memory)

举个例子:
假设为每个执行器分配5个核心, 即–executor-cores = 5
为每个节点留出1个核心用于Hadoop / Yarn守护进程, 即每个节点可用的核心数 = 16-1 = 15。 因此,群集中核心的可用总数= 15 x 10 = 150
–num-executors =(群集中核心的可用总数/每个executors分配的核心数)= 150/5 = 30
为ApplicationManager留下预留1个executors的资源, 即–num-executors = 29
每个节点的executors数目 = 30/10 = 3
群集中每个节点的可使用的总内存数 64GB - 1GB = 63GB
每个executor的内存= 64GB / 3 = 21GB
预留的 off heap overhead = 21GB * 7% ~ 1.47G
所以,实际的–executor-memory = 21 - 1.47G ~ 19GB

几点建议:

org.apache.spark.shuffle.FetchFailedException:Failed to connect to异常
减少使用触发shuffle的操作,例如reduceByKey,从而减少使用内存
增大spark.network.timeout,从而允许有更多时间去等待心跳响应
增加spark.executor.cores,从而减少创建的Executor数量,使得总使用内存减少
同时增大spark.executor.memory,保证每个Executor有足够的可用内存
增大spark.shuffle.memoryFraction,默认为0.2(需要spark.memory.useLegacyMode配置为true,适用于1.5或更旧版本,已经deprecated)

原文链接:https://blog.csdn.net/weixin_42155006/article/details/117118310 

猜你喜欢

转载自blog.csdn.net/lukabruce/article/details/125906892