作业调度
当对RDD执行转换操作时,调度器会根据 RDD 的 lineage(血统)来构建由若干调度阶段(Stage) 组成的有向无环图(DAG),每个调度阶段包含尽可能多的连续窄依赖转换。调度器按照有向 无环图顺序进行计算,并最终得到目标RDD。
调度器向各节点分配任务釆用延时调度机制并根据数据存储位置(数据本地性)来确定。若一个任务需要处理的某个分区刚好存储在某个节点的内存中,则该任务会分配给该节点;如果在内存中不包含该分区,调度器会找到包含该RDD的较佳位置,并把任务分配给所在节点。
对应宽依赖的操作,在Spark将中间结果物化到父分区的节点上,这和MapReduce物化 map的输出类似,可以简化数据的故障恢复过程。如下图所示,实线圆角方框标识的是RDD。阴影背景的矩形是分区,若已存于内存中,则用黑色背景标识。RDD上一个行动操作的执行将 会以宽依赖为分区来构建各个调度阶段,对各调度阶段内部的窄依赖则前后连接构成流水线。在本例中,Stage 1的输出已经存在内存中,所以直接执行Stage 2 ,然后执行Stage 3
Spark如何计算作业调度阶段
对于执行失败的任务,只要它对应调度阶段父类信息仍然可用,该任务会分散到其他节点 重新执行。如果某些调度阶段不可用(例如,因为Shuffle在map节点输出丢失了),则重新提交相应的任务,并以并行方式计算丢失的分区。在作业中如果某个任务执行缓慢(即Straggler), 系统则会在其他节点上执行该任务的副本。该方法与MapReduce推测执行做法类似,并取最先得到的结果作为最终的结果。
调度器
RDD 模型将计算分解为多个相互独立的细粒度任务,这使得它在多用户集群能够支持多种资源共享算法。特别地,每个 RDD 应用可以在执行过程中动态调整访问资源。
- 在每个应用程序中,Spark 运行多线程同时提交作业,并通过一种等级公平调度器来实现多个作业对集群资源的共享,这种调度器和 Hadoop Fair Scheduler 类似。该算法主要用于创建基于针对相同内存数据的多用户应用,例如:Spark SQL引擎有一个服务模式支持多用户并行查询。公平调度算法确保短的作业能够在即使长作业占满集群资源的情况下尽早完成。
- Spark的公平调度也使用延迟调度,通过轮询每台机器的数据,在保持公平的情况下给予作业高的本地性。Spark支持多级本地化访问策略(本地化),包括内存、磁盘和机架。
- 由于任务相互独立,调度器还支持取消作业来为高优先级的作业腾出资源。
- Spark 中可以使用 yarn 来实现细粒度的资源共享,这使得Spark应用能相互之间或在不同的计算框架之间实现资源的动态共享,这也是spark在生产中最常用的调度方式,基于Yarn 做调度。
Spark 提供了uoduo 种持久化 RDD 的存储策略:
- 未序列化Java对象存在内存中
- 序列化的数据存于内存中
- 存储在磁盘中
第一个选项的性能是最优的,因为可以直接访问在Java虚拟机内存里的RDD对象;在空间有限的情况下,第二种方式可以让用户釆用比Java对象更有效的内存组织方式,但代价是降低了性能;第三种策略使用于RDD太大的场景,每次重新计算该 RDD会带来额外的资源开销(如I/O等)。
对于内存使用 LRU 回收算法来进行管理,当计算得到一个新的 RDD 分区,但没有足够空间来存储时,系统会从最近最少使用的 RDD 回收其一个分区的空间。除非该 RDD 是新分区对应的 RDD,这种情况下 Spark 会将旧的分区继续保留在内存中,防止同一个 RDD 的分区被循环调入/调出。这点很关键,因为大部分的操作会在一个 RDD 的所有分区上进行,那么很有可能已经存在内存中的分区将再次被使用。
虽然 lineage 可以用于错误后 RDD 的恢复,但是对于很长的 lineage 的 RDD 来说,这样的恢复耗时比较长,因此需要通过检查点操作(Checkpoint)保存到外部存储中。通常情况下,对于包含宽依赖的长 lineage 的 RDD 设置检查点操作是非常有用的。在这种情况下,集群中某个节点出现故障时,会使得从各个父RDD计算出的数据丢失,造成需要重新计算。相反,对于那些窄依赖的RDD.对其进行检査点操作就不是有必须。在这种情况下如果一个节点发生故障,RDD在该节点中丢失的分区数据可以通过并行的方式从其他节点中重新计算出来,计算成本只是复制RDD的很小部分。Spark提供为RDD设置检查点操作的API,可以让用户自行决定需要为那些数据设置检查点操作。另外由于RDD的只读特性,使得不需要关心数据一致性问题,比常用的共享内存更容易做检查点。参考资料:
Apache Spark 官方文档 https://spark.apache.org/docs/latest
图解 Spark 核心技术与案例实战
- END -