本文主要说一下Spark中Task相关概念、RDD计算时Task的数量、Spark Streaming计算时Task的数量。
Task作为Spark作业执行的最小单位,Task的数量及运行快慢间接决定了作业运行的快慢。
开始
先说明一下Spark作业的几个核心概念:
Job(作业):Spark根据行动操作触发提交作业,以行动操作
将我们的代码切分为多个Job。
Stage(调度阶段):每个Job中,又会根据宽依赖
将Job划分为多个Stage(包括ShuffleMapStage和ResultStage)。
Task(任务):真正执行计算的部分。Stage相当于TaskSet,每个Stage内部包含了多个Task,将各个Task下发到各个Executor执行计算。
每个Task的处理逻辑完全一样,不同的是对应处理的数据。即:移动计算而不是移动数据。
Partition(分区):这个是针对RDD而言的,RDD内部维护了分区列表,表示数据在集群中存放的不同位置。
Job、Stage、Task的对应关系如下:
Task是真正干活的,所以说是它间接决定了Spark程序的快慢也不过分。
再看看Spark任务提交时的几个相关配置:
num-executors
:配置执行任务的Executor的数量。
executor-cores
:每个Executor的核的数量。此核非彼核,它不是机器的CPU核,可以理解为Executor的一个线程。
每个核同时只可以执行一个Task。
也就是说一个Spark应用同时执行的任务数 = 用于执行任务的Executor数 * 每个Executor的核数。
spark.executor.memory
:每个Executor的内存大小。
spark.default.parallelism
:RDD的默认分区数。
在我们没有指定这个参数的前提下,如果是shuffle操作,这个值默认是父RDD中分区数较大的那个值;如果是普通操作,这个值的默认大小取决于集群管理器(YARN, Local这些)。
以YARN为例,如果我们没有指定,它的大小就是所有用于执行任务的Executor核的总数。
spark.sql.shuffle.partitions
:这个配置是针对于Spark SQL在shuffle时的默认分区数。默认值是200。只对Spark SQL起作用。
RDD计算时Task的数量
在基于RDD计算时,Task的数量 = RDD的分区数。
所以调整RDD分区的数量就可以变相的调整Task的数量。
所以当RDD计算跑的很慢时,可以通过适当的调整RDD分区数来实现提速。
看看Spark.parallelize
生成RDD时的源码实现:
def parallelize[T: ClassTag](
seq: Seq[T],
numSlices: Int = defaultParallelism): RDD[T] = withScope {
assertNotStopped()
new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())
}
// 这里的taskScheduler.defaultParallelism就是
// 取的配值中spark.default.parallelism的值。
def defaultParallelism: Int = {
assertNotStopped()
taskScheduler.defaultParallelism
}
可以发现通过Spark.parallelize
创建的RDD分区,如果我们不指定分区数,那么分区数就是由配置的spark.default.parallelism
来决定。
Spark读Hive、HDFS时的Task数量
spark读取hdfs数据时,最终会生成HadoopRDD,所以HadoopRDD的分区数量就是task数量。
以sparkContext.textFile
为例,来分析下HadoopRDD的分区数。
// 有两个重载方法。一个要求指定minPartitions,另一个不做要求。
def textFile(path: String): JavaRDD[String] = sc.textFile(path)
// minPartitions是指希望返回的最小分区数。也就说返回的RDD分区数大于或者等于minPartitions。
def textFile(path: String, minPartitions: Int): JavaRDD[String] =
sc.textFile(path, minPartitions)
一路跟进,最终会调用到org.apache.hadoop.mapred.FileInputFormat
的getSplits
方法,返回的split数就是rdd分区数。
以下是getSplits
方法的实现逻辑,不想看的直接看结论。
// numSplits是期望的分片数量
public InputSplit[] getSplits(JobConf job, int numSplits)