spark 运行的基本流程
前言:
由于最近对 spark 的运行流程非常感兴趣,所以阅读了《Spark 大数据处理:技术、应用与性能优化》一书。通过这本书的学习,了解了 spark 的核心技术、实际应用场景以及性能优化的方法。本文旨在记录和分享下 spark 运行的基本流程。
一、spark 的基础组件及其概念
1. ClusterManager
在 Standalone 模式中即为 Master,控制整个集群,监控 Worker。在 YARN 模式中为资源管理器。
2. Application
用户自定义的 spark 程序, 用户提交后, Spark 为 App 分配资源, 将程序转换并执行。
3. Driver
在 Spark 中,driver 是一个核心概念,指的是 Spark 应用程序的主进程,也称为主节点。负责运行 Application 的 main( ) 函数并创建 SparkContext。
4. Worker
从节点,负责控制计算节点,启动 Executor 或 Driver。在 YARN 模式中为 NodeManager,负责计算节点的控制。
5. Executor
执行器,在 Worker 节点上执行任务的组件、用于启动线程池运行任务。每个 Application 拥有独立的一组 Executors。
6. RDD Graph
RDD 是 spark 的核心结构, 可以通过一系列算子进行操作( 主要有 Transformation 和 Action 操作) 。 当 RDD 遇到 Action 算子时, 将之前的所有算子形成一个有向无环图( DAG) , 也就是 RDD Graph。 再在 Spark 中转化为 Job, 提交到集群执行。一个 App 中可以包含多个 Job。
7. Job
一个 RDD Graph 触发的作业, 往往由 Spark Action 算子触发, 在 SparkContext 中通过 runJob 方法向 Spark 提交 Job。
8. Stage
每个 Job 会根据 RDD 的宽依赖关系被切分很多 Stage, 每个 Stage 中包含一组相同的 Task, 这一组 Task 也叫 TaskSet。
9. Task
一个分区对应一个 Task, Task 执行 RDD 中对应 Stage 中包含的算子。 Task 被封装好后放入 Executor 的线程池中执行。
二、spark 架构
spark 架构采用了分布式计算中的 Master-Slave 模型。Master 作为整个集群的控制器,负责整个集群的正常运行;Worker 相当于是计算节点,接收主节点命令与进行状态汇报;Executor 负责任务的执行;Client 作为用户的客户端负责提交应用,Driver 负责控制一个应用的执行。
![](https://static001.geekbang.org/infoq/78/78cf669b57e1421ef6e092cbf11d6119.png)
如图所示,spark 集群部署后,需要在主节点和从节点分别启动 Master 进程和 Worker 进程,对整个集群进行控制。在一个 spark 应用的执行过程中,Driver 和 Worker 是两个重要角色。Driver 程序是应用逻辑执行的起点,负责作业的调度,即 Task 任务的分发,而多个 Worker 用来管理计算节点和创建 Executor 并行处理任务。在执行阶段,Driver 会将 Task 和 Task 所依赖的 file 和 jar 序列化后传递给对应的 Worker 机器,同时 Executor 对相应数据分区的任务进行处理。
三、Spark 的工作机制
1. Spark 的整体流程
Client 提交应用,Master 找到一个 Worker 启动 Driver,Driver 向 Master 或者资源管理器申请资源,之后将应用转化为 RDD Graph,再由 DAG Scheduler 将 RDD Graph 转化为 Stage 的有向无环图提交给 TaskScheduler,由 TaskScheduler 提交任务给 Executor 执行。
![](https://static001.geekbang.org/infoq/57/571267a00f72b6df54f8d1fd101ff309.png)
如图所示,在 spark 应用中,整个执行流程在逻辑上会形成有向无环图。Action 算子触发之后,将所有累计的算子形成一个有向无环图,然后由调度器调度该图上的任务进行运算。spark 根据 RDD 之间不同的依赖关系切分形成不同的阶段(stage),一个阶段包含一系列函数执行流水线。途中 A、B、C、D、E、F、分别代表不同的 RDD,RDD 内的方框代表分区。数据从 HDFS 输入 spark,形成 RDD A 和 RDD C,RDD C 上执行 map 操作,转换为 RDD D,RDD B 和 RDD E 执行 Join 操作,转换为 F。而在 B 和 E 连接转化为 F 的过程中又会执行 Shuffle,最后 RDD F 通过函数 saveAsSequenceFile 输出并保存到 HDFS 中。
2. Stage 的划分
如上面这个运行流程所示,在 Apache Spark 中,一个作业(Job)通常会被划分为多个阶段(Stage),每个阶段包含一组并行的任务(Task)。这种划分主要是基于数据宽窄依赖进行的,以便更有效地进行任务调度和执行。以下是关于 Spark 中 Stage 划分的一些关键点:
•宽窄依赖
窄依赖(Narrow Dependency):父 RDD 的每个分区只会被一个子 RDD 的分区使用,或者多个子 RDD 分区计算时都使用同一个父 RDD 分区。窄依赖允许在一个集群节点上以流水线的方式(pipeline)计算所有父分区,不会造成网络之间的数据混洗。
宽依赖(Wide Dependency):父 RDD 的每个分区都可能被多个子 RDD 分区所使用,会引起 shuffle。
•Stage 的划分
Spark 根据 RDD 之间的宽窄依赖关系来划分 Stage。遇到宽依赖就划分一个 Stage,每个 Stage 里面包含多个 Task,Task 的数量由该 Stage 最后一个 RDD 的分区数决定。一个 Stage 内部的多个 Task 可以并行执行,而 Stage 之间是串行执行的。只有当一个 Stage 中的所有 Task 都计算完成后,才会开始下一个 Stage 的计算。
•Shuffle 与 Stage 边界
当 Spark 遇到一个宽依赖(如 reduceByKey
、groupBy
等操作)时,它需要在该操作之前和之后分别创建一个新的 Stage。这是因为宽依赖需要 shuffle 数据,而 shuffle 通常涉及磁盘 I/O,因此将宽依赖作为 Stage 之间的边界可以提高效率。
3. Stage 和 Task 调度方式
Stage 的调度是由 DAGScheduler 完成的。 由 RDD 的有向无环图 DAG 切分出了 Stage 的有向无环图 DAG。 Stage 的 DAG 通过最后执行 Stage 为根进行广度优先遍历, 遍历到最开始执行的 Stage 执行, 如果提交的 Stage 仍有未完成的父母 Stage, 则 Stage 需要等待其父 Stage 执行完才能执行。 同时 DAGScheduler 中还维持了几个重要的 Key-Value 集合构, 用来记录 Stage 的状态, 这样能够避免过早执行和重复提交 Stage。waitingStages 中记录仍有未执行的父母 Stage, 防止过早执行。 runningStages 中保存正在执行的 Stage, 防止重复执行。failedStages 中保存执行失败的 Stage, 需要重新执行。
每个 Stage 包含一组并行的 Task,这些 Task 被组织成 TaskSet(任务集合)。DAGScheduler 将划分好的 TaskSet 提交给 TaskScheduler。TaskScheduler 是负责 Task 调度和集群资源管理的组件。TaskScheduler 通过 TaskSetManager 来管理每个 TaskSet。TaskSetManager 会跟踪和控制其管辖的 Task 的执行,包括任务的启动、状态监控和失败重试等。当 TaskSet 被提交到 TaskScheduler 时,TaskScheduler 会决定在哪些 Executor 上运行 Task,并通过集群管理器(如 YARN、Mesos 或 Spark Standalone)将 Task 分发到相应的节点上执行。Executor 接收到 Task 后,会在其管理的线程池中执行任务。执行过程中,Task 的状态会不断更新,并通过状态更新机制通知 TaskSetManager。TaskSetManager 根据接收到的状态更新来跟踪 Task 的执行情况,如遇到任务失败,会触发重试机制直至达到设定的重试次数。
当所有 Task 都执行完成后,TaskScheduler 会通知 DAGScheduler,并由 DAGScheduler 负责触发后续 Stage 的执行(如果存在)。
4. Shuffle 机制
为什么 spark 计算模型需要 Shuffle 过程? 我们都知道, spark 计算模型是在分布式的环境下计算的, 这就不可能在单进程空间中容纳所有的计算数据来进行计算, 这样数据就按照 Key 进行分区, 分配成一块一块的小分区, 打散分布在集群的各个进程的内存空间中, 并不是所有计算算子都满足于按照一种方式分区进行计算。 例如, 当需要对数据进行排序存储时, 就有了重新按照一定的规则对数据重新分区的必要, Shuffle 就是包裹在各种需要重分区的算子之下的一个对数据进行重新组合的过程。
![](https://static001.geekbang.org/infoq/e7/e789c98777e78da725fc40aef4b9c659.png)
****如图, 整个 Job 分为 Stage1~Stage3, 3 个 Stage。首先从最上端的 Stage2、 Stage3 执行, 每个 Stage 对每个分区执行变换( transformation) 的流水线式的函数操作, 执行到每个 Stage 最后阶段进行 Shuffle Write,将数据重新根据下一个 Stage 分区数分成相应的 Bucket, 并将 Bucket 最后写入磁盘。 这个过程就是 Shuffle Write 阶段。执行完 Stage2、 Stage3 之后, Stage1 去存储有 Shuffle 数据节点的磁盘 Fetch 需要的数据, 将数据 Fetch 到本地后进行用户定义的聚集函数操作。 这个阶段叫 Shuffle Fetch, Shuffle Fetch 包含聚集阶段。 这样一轮一轮的 Stage 之间就完成了 Shuffle 操作。
四、结语
在阅读《Spark 大数据处理:技术、应用与性能优化》一书后,我大概了解了 spark 的运行机制及原理。上文仅是做了一个简单的总结,而且并没有对一些细节进行深入解读。在原书中有着十分详细的介绍,包含其容错、IO、网络等机制以及从源码解析 spark 的运行流程,而且书中通过大量实际案例,展示了如何在具体应用中使用 Spark 进行数据处理、分析和挖掘,使理论与实践相结合,大家如有兴趣可自行阅读。
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/e318c2d95ffd94e737a74c2a3】。文章转载请联系作者。
评论