如果我理解正确,有2个(相关的)事情让你感到困惑:
1)什么决定了任务的内容?
2)什么决定了要执行的任务数量?
Spark的引擎"glues"一起 simple 连续rdds上的操作,例如:
rdd1 = sc.textFile( ... )
rdd2 = rdd1.filter( ... )
rdd3 = rdd2.map( ... )
rdd3RowCount = rdd3.count
因此当rdd3(懒惰地)计算时,spark将为rdd1的每个分区生成一个任务,并且每个任务将执行过滤器和每行的映射以产生rdd3 .
任务数由分区数决定 . 每个RDD都有一个定义的分区数量 . 对于从HDFS读取的源RDD(例如,使用sc.textFile(...)),分区数是由输入格式生成的分割数 . RDD上的某些操作可能导致RDD具有不同数量的分区:
rdd2 = rdd1.repartition( 1000 ) will result in rdd2 having 1000 partitions ( regardless of how many partitions rdd1 had ).
另一个例子是加入:
rdd3 = rdd1.join( rdd2 , numPartitions = 1000 ) will result in rdd3 having 1000 partitions ( regardless of partitions number of rdd1 and rdd2 ).
(大多数)改变分区数量的操作涉及一个shuffle,当我们这样做时:
rdd2 = rdd1.repartition( 1000 )
实际发生的是rdd1的每个分区上的任务需要产生一个可以被下一阶段读取的结束输出,以使rdd2有1000个分区(他们是如何做到的?Hash或Sort) . 这方面的任务有时被称为"Map ( side ) tasks" . 稍后在rdd2上运行的任务将作用于一个分区(rdd2!),并且必须弄清楚如何读取/组合与该分区相关的 Map 侧输出 . 这方面的任务有时被称为"Reduce ( side ) tasks" .
这两个问题是相关的:一个阶段中的任务数量是分区的数量(连续的rdds“胶合”在一起)和rdd的分区数量可以在阶段之间改变(通过指定一些分区的数量) shuffle导致操作例如) .
一旦阶段的执行开始,其任务可以占用任务时隙 . 并发任务槽的数量是numExecutors * ExecutorCores . 通常,这些可以由来自不同的非依赖阶段的任务占据 .