文章目录
Spark内存计算框架
Spark SQL
Spark的动态资源划分
- 动态资源划分,主要是 spark 中用于对计算时资源不够或剩余情况下,进行动态资源划分,以求资源的利用率达到最大。
- 在 spark 中,所谓资源单位,一般指的是 executors,和 yarn 中的 containers 一样。
- 在 Spark On Yarn 模式下,通常使用
-num-executors来指定 Application 使用的 executors 数量,而-executor-memory和-executor-cores分别用来指定每个 executor 所使用的内存和虚拟 CPU 核数。
假设有这样的场景,如果使用 Hive,多个用户同时使用 hive-cli 做数据开发和分析,只有当用户提交执行了 Hive SQL 时候,才会向 YARN 申请资源,执行任务。如果不提交执行,无非就是停留在 hive-cli 命令行,也就是个 JVM 而已,并不会浪费 YARN 的资源。
现在想用 Spark-SQL 代替 Hive 来做数据开发和分析,也是多用户同时使用,如果按照之前的方式,以 yarn-client 模式运行 spark-sql 命令行,在启动时候指定–num-executors 10,那么每个用户启动时候都使用了 10 个YARN的资源(Container),这10个资源就会一直被占用着,只有当用户退出 spark-sql 命令行时才会释放。
# 直接通过-e来执行任务,执行完成任务之后,回收资源
spark-sql --master yarn-client \
--executor-memory 512m --num-executors 10 \
--conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse \
--jars /bigdata/install/hadoop-3.1.4/share/hadoop/common/hadoop-lzo-0.4.20.jar \
-e "select count(*) from game_center.ods_task_log;"
# 进入spark-sql客户端,但是不执行任务,一直持有资源(在这种模式下,就算你不提交资源,申请的资源也会一直常驻,这样就明显不合理了)
spark-sql --master yarn-client \
--executor-memory 512m --num-executors 10 \
--conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse \
--jars /bigdata/install/hadoop-3.1.4/share/hadoop/common/hadoop-lzo-0.4.20.jar
Spark SQL On Yarn spark-sql On Yarn,能不能像 Hive 一样,执行 SQL 时才去申请资源,不执行的时候就释放掉资源呢?
- 从 Spark1.2 之后,对于 On Yarn 模式,已经支持动态资源分配(Dynamic Resource Allocation)。这样,就可以根据 Application 的负载(Task情况),动态的增加和减少 executors。这种策略非常适合在 YARN 上使用 spark-sql 做数据开发和分析,以及将 spark-sql 作为长服务来使用的场景。
- spark 当中支持通过动态资源划分的方式来实现动态资源的配置,尽量减少内存的持久占用,但是动态资源划分又会产生进一步的问题,例如:
- executor 动态调整的范围是多少?无限减少,还是无限增加?
- executor 动态调整速率?线性增减,还是指数增减?
- 何时移除 executor?
- 何时新增 executor?只要有新提交的 Task 就新增 Executor 吗?
- Spark 中的 Executor 不仅仅提供计算能力,还可能存储持久化数据,这些数据在宿主 executor 被 kill 后,该如何访问?
- 通过 spark-shell 当中最简单的 WordCount 为例来查看 spark 当中的资源划分,打开命令行终端,依次执行以下命令:
# ① 以yarn模式执行,并指定executor个数为1
$ spark-shell --master=yarn --num-executors=1
# ② 提交Job1 WordCount
scala> sc.textFile("/bigdata/job1.txt").flatMap(line => line.split(" ")).map(word => (word,1)).reduceByKey(_ + _).count();
# ③ 提交Job2 WordCount
scala> sc.textFile("/bigdata/job2.txt").flatMap(line => line.split(" ")).map(word => (word,1)).reduceByKey(_ + _).count();
# ④ Ctrl+C Kill JVM
- 上述的 Spark 应用中,以 yarn 模式启动 spark-shell,并顺序执行两次 WordCount,最后 Ctrl+C 退出 spark-shell。此例中Executor的生命周期如下图:

- 从上图可以看出,Executor 在整个应用执行过程中,其状态一直处于 Busy(执行Task)或 Idle(空等)。处于 Idle 状态的 Executor 造成资源浪费这个问题已经在上面提到,下面重点看下开启Spark动态资源分配功能后 Executor 如何运作。

- ① spark-shell Start:启动 spark shell 应用,并通过
--num-executor指定一个执行器; - ② Executor1 Start:启动执行器 Executor1。注意:Executor 启动前存在一个 AM 向 RM 申请资源的过程,所以启动时机略微滞后于Driver;
- ③ Job1 Start:提交第一个 WordCount 作业,此时,Executor1 处于 Busy 状态;
- ④ Job1 End:作业1结束,Executor1 又处于 Idle 状态;
- ⑤ Executor timeout:Executor1 空闲一段时间后,超时被 Kill;
- ⑥ Job Submit:提交第二个 WordCount,此时,没有 Active 的 Executor 可用,Job2 处于 Pending 状态;
- ⑦ Executor2 Start:检测到 Pending 状态的任务,此时 Spark 会启动 Executor2;
- ⑧ Job2 Start:此时已经有Active 的执行器,Job2 会被分配到 Executor2 上执行;
- ⑨ Job2 End:Job2 执行结束;
- ⑩ Executor2 End:Ctrl + C 杀死 Driver后,Executor2 也会被 RM 杀死。
上述流程中需要重点关注的几个问题:
- Executor 超时:当 Executor 不执行任何任务时,会被标记为 Idle 状态,空闲一段时间后即被认为超时,会被 kill 掉。该空闲时间由 spark.dynamicAllocation.executorIdleTimeout 决定,默认 60s,对应图中Job1 End 到 Executor timeout之间的时间。
- 资源不足时,何时新增 Executor:当有 Task 处于 Pending 状态,意味着资源不足,此时需要增加 Executor。这段时间由 spark.dynamicAllocation.schedulerBacklogTimeout 控制,默认 1s,对应图中Job2 Submit 到 Executor2 Start之间的时间。
- 该新增多少 Executor:新增 Executor 的个数主要依据是当前负载情况,即 running 和 Pending 任务数、以及当前 Executor 个数决定。
- 用 maxNumExecutorsNeeded 代表当前实际需要的最大 Executor 个数,maxNumExecutorsNeeded 和当前 Executor 个数的差值即为潜在的新增 Executor 个数。
- 注意:之所以说潜在个数,是因为最终新增的 Executor 个数还有别的因素需要考虑,后面再分析。下面是 maxNumExecutorsNeeded 计算方法:
private def maxNumExecutorsNeeded(): Int = {
val numRunningOrPendingTasks = listener.totalPendingTasks + listener.totalRunningTasks
math.ceil(numRunningOrPendingTasks * executorAllocationRatio /
tasksPerExecutorForFullParallelism)
.toInt
}
- 其中,numRunningOrPendingTasks 为当前 running 和 pending 任务数之和。
- executorAllocationRatio:最理想的情况下,有多少待执行的任务,那么我们就新增多少个 Executor,从而达到最大的任务并发度。但是这也有副作用,如果当前任务都是小任务,那么这一策略就会造成资源浪费。可能最后申请的 Executor 还没启动,这些小任务就已经被执行完了。该值是一个系数值,范围在 [0, 1],默认值 1。
- tasksPerExecutorForFullParallelism:每个 Executor 的最大并发数,简单理解为 cpu核心数(spark.executor.cores)/ 每个任务占用的核心数(spark.task.cpus)。
1. Executor动态调整范围?
executor动态调整的范围?无限减少?无限制增加?调整速率?
- 要实现资源的动态调整,那么限定调整范围是最先考虑的事情,Spark通过下面几个参数实现:
- spark.dynamicAllocation.minExecutors:Executor 调整下限(默认值:0)
- spark.dynamicAllocation.maxExecutors:Executor 调整上限(默认值:Integer.MAX_VALUE)
- spark.dynamicAllocation.initialExecutors:Executor 初始化数量(默认值:minExecutors)

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



