在 Apache Spark 中,Job 是调度和执行的核心对象之一,它代表了一次具体的数据计算操作。Job 不是独立存在的,而是与 Spark 内部多个组件、类和功能紧密集成。为了全面了解与 Job 相关的所有类、组件和功能,并解释它们之间的关系,我们需要从整个 Spark 的执行流程、架构层次和源代码入手。
1. Job 的概念与架构
在 Spark 中,每个 Job 由用户操作触发,通常对应于一次 Action 操作(如 count(), collect())。这些 Action 操作会让 Spark 将一系列的 RDD 转换操作(transformation,如 map(), filter())封装成一个 Job,并通过调度系统执行。
Job 是 Spark 的逻辑计算单元,但它的执行依赖于底层调度机制和任务划分。在底层,Job 被分解为多个 Stage,每个 Stage 包含一系列可并行执行的 Task。所有这些组件通过不同的类和模块协调工作。
2. Job 相关的核心类和组件
Job 涉及的主要类和组件包括以下几个重要部分:
DAGScheduler:Job的生成与调度管理器。Stage:将Job细化为可调度的执行阶段。Task和TaskSet:进一步将Stage分解为具体的计算任务,映射到集群上的不同节点执行。ActiveJob:表示运行时Job的封装,包含Job的执行状态、监听器等。TaskScheduler:任务的具体调度器,将TaskSet提交给执行器(executor)执行。MapOutputTracker:跟踪 Shuffle 数据的输出位置,用于 Stage 之间的依赖解析。SchedulerBackend:负责与集群管理器(如 YARN、Mesos、Kubernetes)交互,资源分配、任务调度。
下面逐一详细解释这些类及其与 Job 的关系。
3. DAGScheduler:作业调度器
DAGScheduler 是 Spark 的核心调度器,它的主要职责是将用户提交的高层次操作(如 transformations 和 actions)解析为 Job、Stage 和 Task。DAGScheduler 处理了所有与 Job 相关的依赖、划分和调度工作。
- Job 的创建 :当用户调用
Action时,DAGScheduler会创建一个新的Job。 - Stage 的划分 :
DAGScheduler负责将 DAG(有向无环图)划分为多个Stage,每个Stage可以看作是一组可以并行执行的任务。宽依赖(如shuffle操作)会将 DAG 分割成不同的Stage。 - Task 的生成与提交 :每个
Stage被划分为多个Task,Task会被TaskScheduler提交给具体的集群节点执行。
Scala
// DAGScheduler.scala (简化)
class DAGScheduler(
taskScheduler: TaskScheduler,
mapOutputTracker: MapOutputTracker,
...) {
def submitJob[T, U](
rdd: RDD[T],
func: (TaskContext, Iterator[T]) => U,
partitions: Seq[Int],
...) : JobWaiter[U] = {
val jobId = nextJobId.getAndIncrement()
val finalStage = createResultStage(rdd, func, partitions, jobId)
val job = new Job(jobId, finalStage, ...)
jobIdToActiveJob(jobId) = new ActiveJob(job, finalStage)
submitStage(finalStage)
return job.waiter
}
private def submitStage(stage: Stage): Unit = {
if (stage.tasks == null) {
stage.tasks = createShuffleMapTasks(stage)
}
taskScheduler.submitTasks(new TaskSet(stage.tasks, stage.id))
}
}
Job的调度与执行 :DAGScheduler负责提交每个Stage,并通过TaskScheduler调度任务的执行。每个Stage的任务执行完成后,如果该阶段是Job的最后阶段,则Job完成。
4. Stage:执行阶段
Stage 是 Spark 中 Job 的子单元,代表一组可以并行执行的任务。DAGScheduler 会将整个 Job 划分为多个 Stage。每个 Stage 通常对应于一组 RDD 的窄依赖操作。宽依赖操作(如 reduceByKey, groupBy)会引入新的 Stage。
Scala
// Stage.scala
private[scheduler] class Stage(
val id: Int,
val rdd: RDD[_],
val numTasks: Int,
val parents: List[Stage],
val jobId: Int) {
var tasks: Array[Task[_]] = _
def addParent(stage: Stage): Unit = {
parents :+= stage
}
}
- 父子关系 :每个
Stage可以有多个父Stage,表示依赖关系。Stage之间通过RDD的依赖关系建立。
5. Task 和 TaskSet
Task 是 Spark 中的最小计算单元。每个 Stage 被细化为多个 Task,每个 Task 通常对应于 RDD 的一个分区。TaskSet 是一组可并行执行的 Task,它们会被提交给集群中的执行器执行。
Scala
// Task.scala
abstract class Task[T](val taskId: Int, val stageId: Int) extends Serializable {
def run(taskContext: TaskContext): T
}
// TaskSetManager.scala
class TaskSetManager(taskSet: TaskSet, ...) {
def resourceOffer(execId: String, host: String): Option[TaskDescription] = {
val task = findTaskToRun(execId, host)
Option(new TaskDescription(task))
}
}
TaskSet的提交 :TaskSet会由DAGScheduler通过TaskScheduler提交,并在集群中执行。
6. TaskScheduler:任务调度器
TaskScheduler 负责具体的任务调度,它接收来自 DAGScheduler 的 TaskSet,并决定将这些任务分配到集群中的哪个节点执行。TaskScheduler 将任务提交给 SchedulerBackend,并通过集群管理器(如 YARN、Kubernetes)来启动和管理执行器。
Scala
// TaskSchedulerImpl.scala
class TaskSchedulerImpl(backend: SchedulerBackend, ...) extends TaskScheduler {
override def submitTasks(taskSet: TaskSet): Unit = {
val manager = new TaskSetManager(taskSet, maxTaskFailures)
taskSetsByStageIdAndAttempt(taskSet.stageId)(taskSet.stageAttemptId) = manager
backend.reviveOffers()
}
}
- 任务调度与执行器交互 :
TaskScheduler会使用SchedulerBackend与集群管理器进行通信,将任务分配给具体的执行器。
7. SchedulerBackend:与集群管理器的交互
SchedulerBackend 是 Spark 与集群管理器(如 YARN、Mesos、Kubernetes)之间的接口。它负责申请资源并将任务分发给集群中的执行器。
Scala
// CoarseGrainedSchedulerBackend.scala
class CoarseGrainedSchedulerBackend(scheduler: TaskSchedulerImpl, ...) extends SchedulerBackend {
override def start() {
// 启动执行器
super.start()
}
def launchTasks(taskDescriptions: Seq[TaskDescription]) {
for (desc <- taskDescriptions) {
executor.launchTask(desc)
}
}
}
- 任务分配与资源管理 :
SchedulerBackend启动执行器,并在执行器上启动任务。
8. ActiveJob:运行时 Job 封装
ActiveJob 代表一个正在运行的 Job,它封装了 Job 的执行状态、最终结果以及监听器等。DAGScheduler 通过 ActiveJob 追踪 Job 的执行进度。
Scala
// ActiveJob.scala
private[scheduler] class ActiveJob(
val jobId: Int,
val finalStage: Stage,
val listener: JobListener) {
def jobFinished(): Unit = {
listener.jobSucceeded(this)
}
}
ActiveJob的作用 :当Job完成时,ActiveJob会通知JobListener,并进行相应的状态更新。
9. MapOutputTracker:Shuffle 依赖的输出跟踪
MapOutputTracker 负责跟踪 Shuffle 操作的输出位置。它是 Spark 在 Stage 之间执行数据交换的重要组件,尤其是在处理宽依赖时,用于决定下游 Stage 应从哪个节点获取数据。
Scala
// MapOutputTracker.scala
class MapOutputTrackerMaster(conf: SparkConf) extends MapOutputTracker(conf) {
def registerMapOutput(
shuffleId: Int, mapId: Int, status: MapStatus): Unit = {
shuffleStatuses(shuffleId).addMapStatus(mapId, status)
}
}
- Shuffle 依赖解析 :
MapOutputTracker在Stage之间的数据传输过程中,决定数据的获取方式。
10. JobListener:监听 Job 的完成
JobListener 是用于监听 Job 执行状态的接口,当 Job 成功或失败时,JobListener 会通知相关组件并进行状态更新。
Scala
// JobListener.scala
trait JobListener {
def jobSucceeded(job: ActiveJob): Unit
def jobFailed(job: ActiveJob, exception: Exception): Unit
}
11. 组件之间的关系总结
DAGScheduler:负责整个Job的生命周期管理,包括阶段划分、任务调度、任务失败重试等。Stage:Job被分解为多个Stage,每个Stage对应于一组可并行执行的Task。Task和TaskSet:Stage被分解为多个Task,并通过TaskSet提交执行。TaskScheduler和SchedulerBackend:TaskScheduler负责将任务调度到集群中具体的节点,SchedulerBackend负责与集群管理器交互,启动执行器并分配任务。ActiveJob:代表正在运行的Job,并与JobListener一起跟踪其状态。MapOutputTracker:负责Shuffle操作中,数据的输出位置跟踪与管理。
总结
Job是 Spark 中的核心执行单元,代表一次具体的计算操作。Job由用户提交的Action操作触发,并通过DAGScheduler进行调度。DAGScheduler将Job分解为多个Stage,每个Stage进一步被分解为多个Task,这些任务通过TaskScheduler调度到集群的执行器上执行。- 各组件之间的协作 确保了 Spark 中大规模并行计算的高效执行。