Spark 核心概念与基本框架梳理

RDD

Dependencies

建立 RDD 的依赖关系,主要 RDD 之间是宽窄依赖的关系,具有窄依赖关系的 RDD 可以在同一个stage中进行计算,宽窄依赖关系是划分 stage 的重要依据。

窄依赖

父 RDD 的 partition 至多被一个子 RDD partition 依赖,表现为一个父 RDD 的分区对应于一个子 RDD 的分区,和两个父 RDD 的分区对应于一个子 RDD 的分区。图中,map/filter 和 union 属于第一类,对输入进行协同划分(co-partitioned)的 join 属于第二类。

宽依赖

父 RDD 的 partition 被多个子 RDD partitions 依赖,子 RDD 的分区依赖于 父 RDD 的所有分区,这是因为 shuffle 类操作,如图中的 groupByKey 和未经协同划分的 join。

WechatIMG97

Partition

0Canvas 1

输入可能以多个文件的形式存储在 HDFS 上,每个File都包含了很多块,称为 Block
当 Spark 读取这些文件作为输入时,会根据具体数据格式对应的 InputFormat 进行解析,一般是将若干个 Block 合并成一个输入分片,称为 InputSplit,注意 InputSplit 不能跨越文件。
随后将为这些输入分片生成具体的 Task。InputSplit 与 Task 是一一对应的关系。
随后这些具体的 Task 每个都会被分配到集群上的某个节点的某个 Executor 去执行。

至于 partition 的数量:

  • 对于数据读入阶段,例如 sc.textFile,输入文件被划分为多少 InputSplit 就会需要多少初始 Task。
  • 在 Map 阶段 partition 数目保持不变。
  • 在 Reduce 阶段,RDD 的聚合会触发 shuffle 操作,聚合后的 RDD 的 partition 数目跟具体操作有关,例如repartition 操作会聚合成指定分区数,还有一些算子是可配置的。

Partitioner

只存在于(K,V)类型的 RDD 中,非(K,V)类型的 partitioner 的值就是 None。

Preferedlocations

按照“移动数据不如移动计算”原则,在spark进行任务调度的时候,优先将任务分配到数据块存储的位置

Compute

spark中的计算都是以分区为基本单位的,compute函数只是对迭代器进行复合,并不保存单次计算的结果。

RDD 的算子

RDD 的算子主要分成2类,Action 和 Transformation。这里的算子概念,可以理解成就是对数据集的变换。Action 会触发真正的作业提交,而 Transformation 算子是不会立即触发作业提交的。每一个 Transformation() 方法返回一个新的 RDD。只是某些 Transformation() 比较复杂,会包含多个子 Transformation(),因而会生成多个 RDD。这就是实际 RDD 个数比我们想象的多一些的原因。

Spark Application

版面 2

Application 分为三部分,driver、master 和 worker,集群启动时,Driver 向 Master 申请资源,Woker 负责监控自己节点的内存和 CPU 等状况,并向 Master 回报。一个 Worker 默认情况下分配一个 Executor,分配时可以根据需要可以分配多个 Executor,一个节点如果配置了多个 Executor,就会涉及到性能调优。Worker 可以控制 Executor,必要时可以 kill 掉 Executor。程序运行时是 Driver 和 Executor 进行交互的。

  • 每个节点可以起一个或多个 Executor。
  • 每个 Executor 由若干 core 组成,每个 Executor 的每个 core 一次只能执行一个 Task 。
  • 每个 Task 执行的结果就是生成了目标 RDD 的一个 partiton

注意: 这里的 core 是虚拟的 core 而不是机器的物理 CPU 核,可以理解为就是 Executor 的一个工作线程。

RDD partition 个数是逻辑上把数据分成了多少个片。CPU 核数是物理上用几个 CPU 去执行。集群节点个数,应该是指的实际上起了几个进程在跑作业。RDD partition个数如果很大,就表示逻辑上这个数据可以分成许多小片。但虽然分成许多小片,但你只有一个 CPU 去运行,那也只能一个小片一个小片的串行执行,这涉及到性能优化的问题。

Task 被执行的并发度 = Executor数目 * 每个Executor核数。

Spark 之间应用调度

基本概念

Task 任务 :单个分区数据集上的最小处理流程单元

TaskSet 任务集:一组关联的,但是互相之间没有 Shuffle 依赖关系的任务所组成的任务集

Stage 调度阶段:一个任务集所对应的调度阶段

Job 作业:一次 RDD Action 生成的一个或多个 Stage 所组成的一次计算作业

Stage

通常是,当遇到 Action 算子时会触发一个 Job 的提交,然后反推回去看前面的 Transformation 算子,进而形成一张有向无环图。在 DAG 中又进行 Stage 的划分,划分的依据是依赖是否是 shuffle 的,每个 Stage 又可以划分成若干 Task。接下来的事情就是 Driver 发送 Task 到 Executor,Executor 自己的线程池去执行这些 Task,完成之后将结果返回给 Driver。action 算子是划分不同 job 的依据。Shuffle dependency 是 Stage 划分的依据。Stage 就是一组并行的 Task 组成的。
一个 Stage 是一组并行的 task,一个 Stage 可以被多个 Job 共享;一些 Stage 可能没有运行所有的 RDD 的分区,比如 first 和 lookup,Stage 的划分是通过是否存在 Shuffle 为边界来划分的,Stage 的子类有两个:ResultStage 和 ShuffleMapStage。对于窄依赖生成的是 ResultStage,对于宽依赖生成的是 ShuffleMapStage。

版面 3

DAGScheduler 内部维护了各种 task / stage / job 之间的映射关系表。

🙄就到这儿吧,还没写完。

FYI

Stage、Job、Task

RDD

RDD 核心

Stage 划分

Shuffle

Master、Worker、Driver、Executor 工作流程

Driver、Job、Stage

Spark 基本概念解析

Spark Job 划分 Stage 源码解读

Spark 集群节点个数、RDD 分区个数、cpu 内核个数与并行度关系

Spark 作业调度

【Spark Core】从作业提交到任务调度完整生命周期浅析