目录
一、spark作业基本运行原理
二、资源参数调优
Spark内存管理:
三、amazon集群资源参数示例
资源申请与分配:
我们使用spark-submit提交一个spark作业后,这个作业会启动一个对应的Driver进程。根据使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动(client mode),也可能在集群中某个工作节点启动(cluster mode)。Driver进程本身根据我们设置的参数,占有一定数量的内存和CPU core。而Driver进程要做的第一件事情,就是向集群管理器YARN申请运行spark作业需要使用的资源,这里的资源指的就是executor进程。YARN集群管理器会根据我们为spark作业设置的资源参数,在各个工作节点上,启动一定数量的executor进程,每个executor进程都占有一定数量的内存和CPU core。
Task分配:
在申请到作业执行所需的资源后,Driver进程就会开始调度和执行我们编写的作业代码了。Driver进程会将我们编写的spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批task,然后将这些task分配到各个executor进程中执行。Task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个task处理的数据不同而已。一个stage的所有task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后driver就会调度运行下一个stage。下一个stage的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据,得到我们想要的结果为止。
Spark是根据shuffle类算子来进行stage的划分。如果我们的代码中执行了某个shuffle类算子(比如reduceByKey、join等),那么就会在该算子处,划分出一个stage界限来。可以大致理解为,shuffle算子执行之前的代码会被划分为一个stage,shuffle算子执行以及之后的代码会被划分为下一个stage。因此一个stage刚开始执行时,每个task会从上一个stage的task所在的节点,去通过网络传输拉取需要自己处理的所有key,然后对拉取到的所有相同key使用我们自己编写的算子函数执行聚合操作(比如reduceBykey算子接收的函数)。这个过程就是shuffle。
Executor内存和CPU core
当我们在代码中执行了cache/persist等持久化操作时,根据我们选择的持久化级别的不同,每个task计算出的数据也会保存到executor进程的内存或所在节点的磁盘文件中。
Executor的内存主要分为三块:第一块是让task执行我们自己编写的代码时使用,默认是占executor总内存的20%;第二块是让task通过shuffle过程拉取上一个stage的task输出后,进行聚合等操作时使用,默认占executor总内存的20%;第三块是数据持久化时使用的,默认占executor总内存的60%。
Task的执行速度是跟每个executor进程的CPU core数量有直接关系的。一个cup core 同一时间只能执行一个线程。而每个executor进程上分配到多个task,都是以每个task一条线程的方式,多线程并发运行的。如果CPUcore数量比较充足,而且分配到的task数量比较合理,那么通常来说,可以比较快速和高效地执行完这些task线程。
Driver-memory
Num-executors
Executor-cores
Executor-memory
Spark.default.parallelism
Spark.storage.memoryFraction(Before spark 1.6)
Spark.shuffle.memoryFraction(Before spark 1.6)
spark.memory.fraction (After spark 1.6)
Spark.memory.storageFraction(After spark 1.6)
Before 1.6
对于一个executor,内存由以下三个部分组成:(spark1.6之后的版本,需要设置spark.memory.useLegacyMode=true,下面参数配置才起作用)
Execution memory:这片区域是为了解决shuffles,joins,sorts and aggregations过程中为了避免频繁IO需要的buffer。通过spark.shuffle.memoryFraction配置,默认0.2。
Storage memory:这边区域是为了解决block cache(cache\persist方法),broadcast,以及task results的存储。可以通过spark.storage.memoryFraction设置,默认0.6。
Other memory:给系统预留,因为程序本身运行也是需要内存的,默认0.2。
这种内存分配机制最大的问题是,每块内存的使用都不能超过各自的上限,即使另外一块内存空闲,仍不可被其他部分使用。
After 1.6
Executor 中的内存分为两部分:
User memory:给系统预留,因为程序本身运行也是需要内存的,默认0.25。
Spark memory:这部分内存包含storage memory 和 execution memory两部分,这两部分边界由spark.memory.storageFraction参数设置,默认为0.5,新内存管理模型中的优点是,这个边界不是固定的,如果一个区域内存不够用时,可以从另一区域借用。Execution memory保存的是用来计算的中间结果,如果计算过程中找不到存储的数据,会导致任务失败,因而这部分内存的blocks不会被其他线程的task挤出去。Storage memory 缓存内存中数据,如果数据被驱逐,要想再拿到这些数据时,重新计算就可以。Execution向storage借用空间时,有两种可能:一种,storage memory有空闲空间,可以直接增大execution大小,减小storage memory大小;另一种,storage memory空间已经超过设定的大小,强制将storage memory释放超过设定值的部分空间,还给execution memory。反之亦然,只是execution memory 占用的空间不可释放,借给storage memory使用。
以m4.xlarge实例为例,首先查询各个工作节点最大可使用的CPU和内存资源,/etc/hadoop/conf/yarn-site.xml
yarn.nodemanager.resource.cpu-vcores
4
yarn.nodemanager.resource.memory-mb
12288
资源分配满足最大资源限制:
Executor-num * executor-cores <= 4
Executor-num * executor-memory <= 12g
根据作业具体情况设置资源参数如下:
--driver-memory 2G //driver memory 设置太小,会出现OOM问题,导致任务失败
--executor-memory 10G
--executor-cores 4
--num-executors 1
--conf spark.default.parallelism=200 // nodes-num * executor-num * executor-cores * (2/3)
--conf spark.memory.fraction=0.6 //根据GC 时间调整
--conf spark.memory.storageFraction=0.5 //设定的内存值:heap-reserved memory * 0.6 * 0.5
参考资料:
https://tech.meituan.com/spark_tuning_basic.html
https://blog.csdn.net/Full_Stack_delp/article/details/72878236
https://www.cnblogs.com/dreamfly2016/p/5720526.html
https://amazonaws-china.com/cn/blogs/big-data/submitting-user-applications-with-spark-submit/