# 资源调优
在开发完Spark作业之后,就该为作业配置合适的资源了。Spark的资源参数,基本都可以在spark-submit命令中作为参数设置
**Spark作业基本运行原理**
1. spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程
2. 根据你使用的部署模式(deploy-mode:client/cluster)不同,Driver进程可能在本地启动,也可能在集群中某个工作节点上启动
3. Driver进程本身会根据我们设置的参数,占有一定数量的内存和CPU Core
4. Driver进程要做的第一件事情,就是向集群管理器(可以是Spark Standalone集群,也可以是YARN)申请运行Spark作业需要使用的资源。资源指的就是Executor进程。在各个工作节点上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU Core
5. 在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了
6. Driver进程会将我们编写的Spark作业代码分拆为多个Stage,每个Stage执行一部分代码片段,并为每个Stage创建一批Task,然后将这些Task分配到各个Executor进程中执行
7. Task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们编写的某个代码片段),只是每个Task处理的数据不同而已。一个Stage的所有Task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个Stage。下一个Stage的Task的输入数据就是上一个Stage输出的中间结果
8. Spark是根据Shuffle类算子来进行Stage的划分,Shuffle算子执行之前的代码会被划分为一个Stage
Executor的内存主要分为三块
- 第一块是让task执行我们自己编写的代码时使用,默认是占Executor总内存的20%
- 第二块是让Task通过Shuffle过程拉取了上一个Stage的Task的输出后,进行聚合等操作时使用,默认也是占Executor总内存的20%
- 第三块是让RDD持久化时使用,默认占Executor总内存的60%
参考[Spark内存架构](https://www.jianshu.com/p/02fca6460c37)
Task的执行速度是跟每个Executor进程的CPU Core数量有直接关系的。一个CPU Core同一时间只能执行一个线程。而每个Executor进程上分配到的多个Task,都是以每个Task一条线程的方式,多线程并发运行的。如果CPU Core数量比较充足,而且分配到的Task数量比较合理,那么通常来说,可以比较快速和高效地执行完这些Task线程
* * *
**资源参数调优**
num-executors
设置Spark作业总共要用多少个Executor进程来执行
Driver在向YARN集群管理器申请资源时,YARN集群管理器会尽可能按照你的设置来在集群的各个工作节点上,启动相应数量的Executor进程
> 每个Spark作业的运行一般设置50~100个左右(根据集群的规模)的Executor进程比较合适,设置太少或太多的Executor进程都不好。设置的太少,无法充分利用集群资源;设置的太多的话,大部分队列可能无法给予充分的资源
executor-memory
设置每个Executor进程的内存
Executor内存的大小,很多时候直接决定了Spark作业的性能,而且跟常见的JVM OOM异常,也有直接的关联
> 每个Executor进程的内存设置4G~8G较为合适,num-executors乘以executor-memory,是不能超过队列的最大内存量的,Spark集群可以设置每个executor最多使用的内存大小。如果你是跟团队里其他人共享这个资源队列,那么申请的内存量最好不要超过资源队列最大总内存的1/3~1/2
executor-cores
设置每个Executor进程的CPU core数量
决定了每个Executor进程并行执行task线程的能力
> 数量设置为2~4个较为合适,依据资源队列的最大CPU Core限制是多少,再依据设置的Executor数量,来决定每个Executor进程可以分配到几个CPU Core
driver-memory
设置Driver进程的内存
Driver的内存通常来说不设置,或者设置1G左右应该就够了
> 如果需要使用 collect 算子将RDD的数据全部拉取到Driver上进行处理,那么必须确保Driver的内存足够大,否则会出现OOM内存溢出的问题
spark.default.parallelism
设置每个stage的默认task数量
不去设置这个参数,那么Spark根据底层HDFS的block数量来设置task的数量,默认是一个HDFS block对应一个task,通常来说,Spark默认设置的数量是偏少的
> 设置该参数为num-executors * executor-cores的2~3倍较为合适
> 如果task数量偏少的话,Executor进程可能根本就没有task执行,也就是白白浪费了资源
spark.storage.memoryFraction
设置RDD持久化数据在Executor内存中能占的比例,默认是0.6
根据你选择的不同的持久化策略,如果内存不够时,可能数据就不会持久化,或者数据会写入磁盘
> 如果Spark作业中,有较多的RDD持久化操作,该参数的值可以适当提高一些
> 如果Spark作业中的Shuffle类操作比较多,而持久化操作比较少,那么这个参数的值适当降低一些比较合适
> 如果发现作业由于频繁的GC导致运行缓慢(通过Spark WebUI可以观察到作业的GC耗时),意味着Task执行用户代码的内存不够用,那么同样建议调低这个参数的值
spark.shuffle.memoryFraction
设置Shuffle过程中一个task拉取到上个Stage的Task的输出后,进行聚合操作时能够使用的Executor内存的比例,默认是0.2
Shuffle操作在进行聚合时,如果发现使用的内存超出了这个20%的限制,那么多余的数据就会溢写到磁盘文件中去,此时就会极大地降低性能
> 如果Spark作业中的RDD持久化操作较少,Shuffle操作较多时,建议降低持久化操作的内存占比,提高Shuffle操作的内存占比比例
> 如果发现作业由于频繁的GC导致运行缓慢,意味着Task执行用户代码的内存不够用,那么同样建议调低这个参数的值
示例
#args :
/usr/local/spark/bin/spark-submit --class Process \
--master yarn-cluster \
--name Process \
--queue fetech \
--num-executors 20 \
--driver-memory 5g \
--executor-memory 4g \
--executor-cores 2 \
--conf spark.default.parallelism=500 \
--conf spark.storage.memoryFraction=0.5 \
/process.jar $1 $2 $3 $4
作者:Alex90
链接:https://www.jianshu.com/p/c853997ea1f6
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。