写点什么

Spark 底层原理详细解析 (深度好文,建议收藏)

发布于: 2021 年 01 月 29 日
Spark底层原理详细解析(深度好文,建议收藏)

Spark 简介

Apache Spark 是用于大规模数据处理的统一分析引擎,基于内存计算,提高了在大数据环境下数据处理的实时性,同时保证了高容错性高可伸缩性,允许用户将 Spark 部署在大量硬件之上,形成集群。


Spark 源码从 1.x 的 40w 行发展到现在的超过 100w 行,有 1400 多位大牛贡献了代码。整个 Spark 框架源码是一个巨大的工程。下面我们一起来看下 spark 的底层执行原理。


Spark 运行流程



具体运行流程如下:


  1. SparkContext 向资源管理器注册并向资源管理器申请运行 Executor


  1. 资源管理器分配 Executor,然后资源管理器启动 Executor


  1. Executor 发送心跳至资源管理器


  1. SparkContext 构建 DAG 有向无环图


  1. 将 DAG 分解成 Stage(TaskSet)


  1. 把 Stage 发送给 TaskScheduler


  1. Executor 向 SparkContext 申请 Task


  1. TaskScheduler 将 Task 发送给 Executor 运行


  1. 同时 SparkContext 将应用程序代码发放给 Executor


  1. Task 在 Executor 上运行,运行完毕释放所有资源


1. 从代码角度看 DAG 图的构建


Val lines1 = sc.textFile(inputPath1).map(...).map(...)
Val lines2 = sc.textFile(inputPath2).map(...)
Val lines3 = sc.textFile(inputPath3)
Val dtinone1 = lines2.union(lines3)
Val dtinone = lines1.join(dtinone1)
dtinone.saveAsTextFile(...)
dtinone.filter(...).foreach(...)
复制代码


上述代码的 DAG 图如下所示:



Spark 内核会在需要计算发生的时刻绘制一张关于计算路径的有向无环图,也就是如上图所示的 DAG。


Spark 的计算发生在 RDD 的 Action 操作,而对 Action 之前的所有 Transformation,Spark 只是记录下 RDD 生成的轨迹,而不会触发真正的计算


2. 将 DAG 划分为 Stage 核心算法


一个 Application 可以有多个 job 多个 Stage:


Spark Application 中可以因为不同的 Action 触发众多的 job,一个 Application 中可以有很多的 job,每个 job 是由一个或者多个 Stage 构成的,后面的 Stage 依赖于前面的 Stage,也就是说只有前面依赖的 Stage 计算完毕后,后面的 Stage 才会运行。


划分依据:


Stage 划分的依据就是宽依赖,像 reduceByKey,groupByKey 等算子,会导致宽依赖的产生。


回顾下宽窄依赖的划分原则:

窄依赖:父 RDD 的一个分区只会被子 RDD 的一个分区依赖。即一对一或者多对一的关系,可理解为独生子女。 常见的窄依赖有:map、filter、union、mapPartitions、mapValues、join(父 RDD 是 hash-partitioned)等。

宽依赖:父 RDD 的一个分区会被子 RDD 的多个分区依赖(涉及到 shuffle)。即一对多的关系,可理解为超生。 常见的宽依赖有 groupByKey、partitionBy、reduceByKey、join(父 RDD 不是 hash-partitioned)等。


核心算法:回溯算法


从后往前回溯/反向解析,遇到窄依赖加入本 Stage,遇见宽依赖进行 Stage 切分。


Spark 内核会从触发 Action 操作的那个 RDD 开始从后往前推,首先会为最后一个 RDD 创建一个 Stage,然后继续倒推,如果发现对某个 RDD 是宽依赖,那么就会将宽依赖的那个 RDD 创建一个新的 Stage,那个 RDD 就是新的 Stage 的最后一个 RDD。

然后依次类推,继续倒推,根据窄依赖或者宽依赖进行 Stage 的划分,直到所有的 RDD 全部遍历完成为止。


3. 将 DAG 划分为 Stage 剖析



一个 Spark 程序可以有多个 DAG(有几个 Action,就有几个 DAG,上图最后只有一个 Action(图中未表现),那么就是一个 DAG)


一个 DAG 可以有多个 Stage(根据宽依赖/shuffle 进行划分)。


同一个 Stage 可以有多个 Task 并行执行(*task 数=分区数*,如上图,Stage1 中有三个分区 P1、P2、P3,对应的也有三个 Task)。


可以看到这个 DAG 中只 reduceByKey 操作是一个宽依赖,Spark 内核会以此为边界将其前后划分成不同的 Stage。


同时我们可以注意到,在图中 Stage1 中,从 textFile 到 flatMap 到 map 都是窄依赖,这几步操作可以形成一个流水线操作,通过 flatMap 操作生成的 partition 可以不用等待整个 RDD 计算结束,而是继续进行 map 操作,这样大大提高了计算的效率


4. 提交 Stages


调度阶段的提交,最终会被转换成一个任务集的提交,DAGScheduler 通过 TaskScheduler 接口提交任务集,这个任务集最终会触发 TaskScheduler 构建一个 TaskSetManager 的实例来管理这个任务集的生命周期,对于 DAGScheduler 来说,提交调度阶段的工作到此就完成了。


而 TaskScheduler 的具体实现则会在得到计算资源的时候,进一步通过 TaskSetManager 调度具体的任务到对应的 Executor 节点上进行运算。



5. 监控 Job、Task、Executor


  1. DAGScheduler 监控 Job 与 Task:


要保证相互依赖的作业调度阶段能够得到顺利的调度执行,DAGScheduler 需要监控当前作业调度阶段乃至任务的完成情况。


这通过对外暴露一系列的回调函数来实现的,对于 TaskScheduler 来说,这些回调函数主要包括任务的开始结束失败、任务集的失败,DAGScheduler 根据这些任务的生命周期信息进一步维护作业和调度阶段的状态信息。


  1. DAGScheduler 监控 Executor 的生命状态:


TaskScheduler 通过回调函数通知 DAGScheduler 具体的 Executor 的生命状态,如果某一个 Executor 崩溃了,则对应的调度阶段任务集的 ShuffleMapTask 的输出结果也将标志为不可用,这将导致对应任务集状态的变更,进而重新执行相关计算任务,以获取丢失的相关数据


6. 获取任务执行结果


  1. 结果 DAGScheduler:


一个具体的任务在 Executor 中执行完毕后,其结果需要以某种形式返回给 DAGScheduler,根据任务类型的不同,任务结果的返回方式也不同。


  1. 两种结果,中间结果与最终结果:


对于 FinalStage 所对应的任务,返回给 DAGScheduler 的是运算结果本身。


而对于中间调度阶段对应的任务 ShuffleMapTask,返回给 DAGScheduler 的是一个 MapStatus 里的相关存储信息,而非结果本身,这些存储位置信息将作为下一个调度阶段的任务获取输入数据的依据。


  1. 两种类型,DirectTaskResult 与 IndirectTaskResult


根据任务结果大小的不同,ResultTask 返回的结果又分为两类:


如果结果足够小,则直接放在 DirectTaskResult 对象内中。


如果超过特定尺寸则在 Executor 端会将 DirectTaskResult 先序列化,再把序列化的结果作为一个数据块存放在 BlockManager 中,然后将 BlockManager 返回的 BlockID 放在 IndirectTaskResult 对象中返回给 TaskScheduler,TaskScheduler 进而调用 TaskResultGetter 将 IndirectTaskResult 中的 BlockID 取出并通过 BlockManager 最终取得对应的 DirectTaskResult。


7. 任务调度总体诠释


一张图说明任务总体调度:



Spark 运行架构特点


1. Executor 进程专属


每个 Application 获取专属的 Executor 进程,该进程在 Application 期间一直驻留,并以多线程方式运行 Tasks


Spark Application 不能跨应用程序共享数据,除非将数据写入到外部存储系统。如图所示:



2. 支持多种资源管理器

Spark 与资源管理器无关,只要能够获取 Executor 进程,并能保持相互通信就可以了。


Spark 支持资源管理器包含: Standalone、On Mesos、On YARN、Or On EC2。如图所示:



3. Job 提交就近原则


提交 SparkContext 的 Client 应该靠近 Worker 节点(运行 Executor 的节点),最好是在同一个 Rack(机架)里,因为 Spark Application 运行过程中 SparkContext 和 Executor 之间有大量的信息交换;


如果想在远程集群中运行,最好使用 RPC 将 SparkContext 提交给集群,不要远离 Worker 运行 SparkContext


如图所示:



4. 移动程序而非移动数据的原则执行


移动程序而非移动数据的原则执行,Task 采用了数据本地性和推测执行的优化机制


关键方法:taskIdToLocations、getPreferedLocations。


如图所示:



搜索公众号:五分钟学大数据,带你学习大数据技术!


发布于: 2021 年 01 月 29 日阅读数: 33
用户头像

公众号:五分钟学大数据 2020.11.10 加入

大数据领域原创技术号,专注于大数据技术

评论

发布
暂无评论
Spark底层原理详细解析(深度好文,建议收藏)