在 Apache Spark 中,TaskSet
是任务调度系统的核心对象之一。它代表一组可以并行执行的任务,并通过 TaskScheduler
负责将这些任务分配到不同的执行器(Executor)上执行。每个 TaskSet
通常对应于一个 Stage
的所有任务。
为了全面了解 TaskSet
对象及其工作原理,我们需要从 Spark 的底层架构、任务调度流程以及源代码的角度进行深入探讨。本文将详细解析 TaskSet
对象的构造、调度机制、与其他组件的交互关系,并解释其在 Spark 任务执行中的关键作用。
1. TaskSet
的核心概念
TaskSet
是 Spark 调度系统中的一个基本单位,它封装了一组可以并行执行的任务(Task
),并由 TaskScheduler
调度执行。这些任务一般对应于某个 Stage
的所有分区数据的计算。
每当 Spark 将一个 Stage
提交执行时,会为该 Stage
生成一个 TaskSet
对象。TaskSet
里的每个 Task
对应于一个分区的计算。因此,如果某个 RDD 有 100 个分区,那么对应的 TaskSet
将包含 100 个 Task
,每个 Task
负责处理一个分区的数据。
2. TaskSet
的类定义
在 Spark 源代码中,TaskSet
类主要定义在 TaskSet.scala
文件中。其主要作用是将一组任务(Task
)封装成一个集合,并向调度器提供这些任务以进行调度。以下是 TaskSet
类的简化结构:
Scala
// TaskSet.scala
private[spark] class TaskSet(
val tasks: Array[Task[_]],
val stageId: Int,
val stageAttemptId: Int,
val priority: Int,
val properties: Properties)
TaskSet
的构造函数参数:
tasks
: 任务数组,包含Task
对象,每个Task
对应于一个分区的计算。stageId
: 当前任务集所对应的Stage
的 ID。stageAttemptId
: 当前Stage
的尝试次数(用于处理失败重试)。priority
: 任务的优先级,优先级较高的任务将优先调度执行。properties
: 任务集的配置信息。
3. TaskSetManager
:任务集的管理器
TaskSet
并不是直接调度的,而是通过 TaskSetManager
进行管理和调度。TaskSetManager
是 TaskSet
的管理类,负责对 TaskSet
中的任务进行调度、重试、监控和容错处理。
每次当 TaskSet
提交给 TaskScheduler
时,都会创建一个对应的 TaskSetManager
实例。TaskSetManager
负责在执行期间跟踪 Task
的状态、处理任务失败并执行重试逻辑。每个 TaskSet
可能会经历多次尝试,因此 TaskSetManager
也用于处理任务重试(stage attempt)的相关逻辑。
Scala
// TaskSetManager.scala
private[scheduler] class TaskSetManager(
sched: TaskSchedulerImpl,
val taskSet: TaskSet,
val maxTaskFailures: Int) extends Schedulable {
// 当前 TaskSet 的任务状态
private[scheduler] val taskInfos = Array.fill[TaskInfo](numTasks)(null)
def resourceOffer(execId: String, host: String, maxLocality: TaskLocality): Option[TaskDescription] = {
// 寻找一个适合执行的任务
val task = findTaskToRun(execId, host, maxLocality)
if (task.isDefined) {
Some(new TaskDescription(execId, task.get))
} else {
None
}
}
}
TaskSetManager
的主要职责:
- 任务调度 :当集群中的某个节点可用时,
TaskSetManager
负责为该节点选择合适的任务,并将任务交给执行器执行。 - 任务状态管理 :
TaskSetManager
会跟踪每个任务的状态,包括运行状态、完成状态、失败次数等。 - 本地性调度 :
TaskSetManager
实现了任务的本地性调度(data locality),尽可能将任务调度到数据所在的节点上执行,以提高性能。 - 任务重试 :如果某个任务执行失败,
TaskSetManager
会负责对该任务进行重试,直到超过最大失败次数为止。
4. TaskScheduler
和 TaskSet
的交互
TaskSet
是通过 TaskScheduler
调度的。在 Spark 的调度架构中,TaskScheduler
负责接收来自 DAGScheduler
的 TaskSet
,并将其调度到集群中的执行器上执行。TaskScheduler
会根据集群资源的状态,选择合适的任务进行调度。
TaskScheduler
的主要职责是:
- 提交任务集 :当
DAGScheduler
将Stage
分解为TaskSet
后,会将该TaskSet
提交给TaskScheduler
。 - 调度任务 :
TaskScheduler
会调用TaskSetManager
的方法,根据集群资源的可用情况调度任务到执行器。 - 监控任务执行 :
TaskScheduler
负责监控每个任务的执行情况,并处理失败的任务。
Scala
// TaskSchedulerImpl.scala
class TaskSchedulerImpl(
sc: SparkContext,
...) extends TaskScheduler {
override def submitTasks(taskSet: TaskSet): Unit = {
val manager = new TaskSetManager(this, taskSet, maxTaskFailures)
taskSetsByStageIdAndAttempt(taskSet.stageId)(taskSet.stageAttemptId) = manager
backend.reviveOffers()
}
def resourceOffer(execId: String, host: String, maxLocality: TaskLocality): Option[TaskDescription] = {
taskSetsByStageIdAndAttempt.foreach { case (_, managers) =>
managers.foreach { manager =>
val taskOption = manager.resourceOffer(execId, host, maxLocality)
if (taskOption.isDefined) {
return taskOption
}
}
}
None
}
}
任务提交流程:
DAGScheduler
生成TaskSet
,并通过submitTasks
提交给TaskSchedulerImpl
。TaskSchedulerImpl
创建对应的TaskSetManager
,并启动任务调度过程。- 每当集群资源(执行器)可用时,
TaskSchedulerImpl
会调用TaskSetManager
的resourceOffer
方法,将任务分配到可用的执行器上。
5. 本地性调度 (Task Locality)
Spark 在调度任务时,会尽量遵循数据本地性(Data Locality)原则。所谓数据本地性,就是将任务调度到存储其输入数据的节点上执行,以减少数据传输的开销。
TaskSetManager
的 resourceOffer
方法会根据任务的本地性需求,选择合适的任务调度到相应的执行器上。Spark 定义了几种不同的任务本地性级别(TaskLocality
):
PROCESS_LOCAL
:任务可以在同一进程中运行,最佳本地性。NODE_LOCAL
:任务可以在同一节点上运行,但可能需要在本地磁盘之间传输数据。RACK_LOCAL
:任务可以在同一机架中的节点上运行。ANY
:任务可以在集群中的任何节点上运行,这是最低的本地性级别。
当 TaskScheduler
接收到资源调度请求时,它会根据这些本地性级别尝试分配任务。如果没有合适的任务,系统会在经过一段时间后放宽本地性要求,允许任务在其他节点上执行。
Scala
// TaskLocality.scala
object TaskLocality extends Enumeration {
val PROCESS_LOCAL, NODE_LOCAL, NO_PREF, RACK_LOCAL, ANY = Value
}
在 TaskSetManager
的 resourceOffer
中,会根据可用的执行器位置和任务的本地性要求,决定是否分配该任务到该执行器上。
6. 任务失败与重试机制
任务失败在分布式系统中是常见的情况。Spark 通过 TaskSetManager
实现了任务的重试机制。当某个任务执行失败时,TaskSetManager
会负责对该任务进行重试,直到任务成功或超过最大失败次数为止。
- 最大失败次数 :每个
TaskSet
都有一个最大失败次数(maxTaskFailures
),默认为 4 次。如果某个任务失败次数超过这个阈值,则整个Stage
失败。 - 失败重试 :如果任务失败,
TaskSetManager
会选择其他节点进行重试。失败可能是由于节点故障、内存不足等原因。
Scala
// TaskSetManager.scala
private[scheduler] class TaskSetManager(
sched: TaskSchedulerImpl,
val taskSet: TaskSet,
val maxTaskFailures: Int) {
def handleFailedTask(tid: Long, state: TaskState, reason: TaskFailedReason): Unit = {
taskInfos(tid).failedAttempts += 1
if (taskInfos(tid).failedAttempts >= maxTaskFailures) {
abort(s"Task $tid failed ${taskInfos(tid).failedAttempts} times")
} else {
// 重试任务
addPendingTask(tid)
}
}
}
在重试时,TaskSetManager
会尝试将任务分配到不同的节点,以避免再次失败。
7. TaskSet
的执行流程总结
整个 TaskSet
的执行流程可以总结如下:
- 用户提交的
Action
操作触发 Spark 的DAGScheduler
,并将计算分解为多个Stage
。 - 每个
Stage
会生成一个TaskSet
对象,包含所有任务(分区)并提交给TaskScheduler
。 TaskScheduler
创建TaskSetManager
并负责调度TaskSet
中的任务。TaskSetManager
根据可用的资源和数据本地性,将任务分配给合适的执行器。- 执行器执行任务并返回结果。如果任务失败,
TaskSetManager
会处理重试逻辑。 - 当所有任务成功执行后,
TaskSet
执行完成,Stage
也标记为完成。
8. 结论
TaskSet
是 Apache Spark 中任务调度的重要组成部分,代表了可以并行执行的一组任务。TaskSetManager
负责管理这些任务的执行、调度和重试机制。TaskSet
与 TaskScheduler
紧密协作,通过数据本地性优化、任务失败重试等机制确保高效的任务调度和执行。
从底层原理和源代码的角度来看,TaskSet
是 Spark 提高并行计算效率、容错性和任务调度性能的重要机制之一。