订阅更新

Spark 中 Job、Stage 和 Task 是怎么划分的?

面试问 058 约 3 分钟读完 2 次阅读

本文目录
  1. 面试怎么回答
  2. Job:一次 Action 触发的计算
  3. Stage:沿着 Shuffle 边界切分
  4. Task:Stage 内的最小执行单元
  5. 一个典型排查场景
  6. 三个常见误区

面试怎么回答

Spark 中 Action 会触发 Job,DAG Scheduler 按 Shuffle 等宽依赖边界把 Job 切成多个 Stage;Stage 内窄依赖可以流水线执行。每个 Stage 按分区生成 Task,一个 Task 处理一个分区,是 Executor 上的最小执行单元。排查时先定位慢 Job,再定位慢 Stage,最后对比 Task 的耗时、输入量、Shuffle、GC 和 Spill,判断整体慢还是少数长尾。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

如果只背“Job 包含 Stage,Stage 包含 Task”,还没有解释清楚它们为什么这样划分。

真正要抓住的是三条线:Action 触发 Job,Shuffle 划分 Stage,分区决定一个 Stage 中的并行 Task。


Job:一次 Action 触发的计算

RDD 的 map、filter 等 Transformation 是惰性执行的。只有遇到 count、collect、save 等 Action,Spark 才会提交真正的计算。

从概念上看,一个 Action 通常对应一个 Job。一个应用可以包含多个 Action,因此也可能产生多个 Job。

在 Spark SQL 场景里,广播子查询、统计收集或执行策略也可能让界面里出现多个关联 Job,所以排查时要结合 SQL Tab 和 Jobs Tab,而不是只按代码行数猜。

Stage:沿着 Shuffle 边界切分

Spark 会先根据依赖关系形成 DAG。

窄依赖中,一个父分区只被少量固定子分区依赖,数据可以在同一条流水线里连续计算,通常放在同一个 Stage。

宽依赖需要重新分区,同一个父分区的数据可能发往多个子分区,必须经过 Shuffle。这个物化与拉取边界会把前后计算切成不同 Stage。

Task:Stage 内的最小执行单元

一个 Stage 会根据要处理的分区生成一组 Task。每个 Task 负责一个分区上的同一套计算逻辑,并被调度到 Executor 执行。

因此某个 Stage 有多少 Task,通常与该 Stage 的分区数一致。并行度太低,资源用不满;分区太多,又会增加调度和小任务开销。

Job、Stage、Task 划分关系


一个典型排查场景

比如一个 Job 有三个 Stage,前两个几十秒完成,最后一个 Stage 却跑了十几分钟。

这时不能只说“整个 Job 很慢”,而要继续下钻:

  • 最后一个 Stage 是否发生了大量 Shuffle Read
  • Task 数是否合理
  • 是否只有少数 Task 特别慢
  • 慢 Task 的输入量、GC、反序列化或 Spill 是否异常

如果 999 个 Task 都很快,只有 1 个 Task 长时间不结束,问题通常不在 Job 数量,而在这个 Stage 内的数据倾斜、资源异常或分区热点。

Job 帮你定位一次业务计算,Stage 帮你定位哪段依赖链慢,Task 才能暴露真正的长尾。


三个常见误区

一个 Transformation 就是一个 Stage

不对。多个窄依赖 Transformation 可以流水线执行,只有遇到 Shuffle 等阶段边界才会切分。

Task 越多,并行度一定越高

Task 数量还要受 Executor Core、资源调度和数据量约束。大量很小的 Task 只会增加调度成本。

Stage 越多,任务一定越慢

Stage 多说明 Shuffle 或阶段边界多,但性能还要看数据规模、倾斜、网络、内存与磁盘。不能只用 Stage 数量下结论。


刷题进度保存在当前浏览器