目录
DAG概述
Spark的核心是根据RDD来实现的,Spark Schedule则为Spark核心实现的重要一环,其作用就是任务调度。Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据,根据RDD的依赖关系构建DAG,基于DAG划分Stage,将每个Stage中的任务发到指定节点运行。基于Spark的任务调度原理,可以合理规划资源利用,做到尽可能用最少的资源高效地完成任务计算。
以WordCount程序为例,DAG图:
DAG(Directed Acyclic Graph)有向无环图是由点和线组成的拓扑图形,该图形具有方向,不会闭环。例如,DAG记录了RDD的转换过程和任务的阶段。
简化后的DAG图:
Job和Action算子
一个Action算子(比如collect)会将之前的一串RDD依赖链条执行起来。
一个Action算子就会产生一个DAG图。
总结:
一个Action=1个DAG=1个Job
1个应用程序中可以含多个Action算子,也就是会产生多个DAG图,启动多个Job。
带有分区关系的DAG图:
血缘关系
描述了数据处理过程中从原始数据源到最终结果的整个计算过程和依赖关系。
示例(方法逻辑可以优化,此处仅为示例)
计算字符串列表RDD_0
"1 2 3"中的数字最大值
- 拆分:
RDD_1
=拆分后的每个字符"1" "2" "3" - 格式转换:
RDD_2
=int类型的1 2 3 - 排序: 按照升序排列
RDD_3
=排序后 123 - 取值:
RDD_4
=最末值3
RDD_4
依赖于RDD_3
依赖于RDD_2
依赖于RDD_1
依赖于RDD_0
血缘关系的作用
- 故障恢复:当Spark集群中的某个节点发生故障导致数据丢失时,Spark可以根据血缘信息重新计算丢失的数据块。这是因为每个RDD都记录了其父RDD以及创建它的转换操作,从而可以通过这些信息来恢复丢失的数据。
- 优化执行计划:Spark的Catalyst优化器可以通过分析血缘DAG来优化任务调度。例如,通过合并连续的操作、减少shuffle阶段等方式来提高整体执行效率。
- 可视化和调试:开发者可以通过toDebugString方法将RDD的血缘信息转化为字符串,显示RDD的层次结构和转换过程。这对于理解和调试复杂的数据处理流程非常有帮助。
- 缓存和持久化:如果某个RDD被缓存或持久化,那么根据血缘关系,其依赖于它的所有后续操作都可以直接从缓存中获取数据,从而加速计算过程。
宽窄依赖关系
窄依赖表示每一个父(上游)RDD的Partition最多被子(下游)RDD的一个分区(Partition)使用。
宽依赖表示同一个父(上游)RDD的Partition被多个子(下游)RDD的Partition依赖,会引起Shuffle。
Shuffle:将数据重新分布到不同的节点或分区上,以便进行后续的聚合或连接操作。
算子的宽窄依赖划分
阶段划分:
按照宽依赖划分不同的DAG阶段,划分依据为从后向前,遇到宽依赖就划分一个阶段,被称为Stage.
Stage:代表了一组可以并行执行的任务(Tasks)集合,由一组可以在数据上进行转换和操作的并行任务组成。
Stage(并行、依赖顺序执行)
任务调度
一个Spark应用程序包括Job、Stage以及Task三个概念:
- Job是以Action方法为界,遇到一个Action方法则触发一个Job;
- Stage是Job的子集,以RDD宽依赖(即Shuffle)为界,遇到Shuffle做一次划分;
- Task是Stage的子集,以并行度(分区数)来衡量,分区数是多少,则有多少个task。
Spark的任务调度总体来说分两路进行,一路是Stage级的调度,一路是Task级的调度,总体调度流程如下图所示:
具体任务调度流程
- Driver初始化SparkContext过程中,会分别初始化DAGScheduler、TaskScheduler、SchedulerBackend以及HeartbeatReceiver,并启动SchedulerBackend以及HeartbeatReceiver。
- DAGScheduler负责Stage级的调度,主要是将job切分成若干Stages,并将每个Stage打包成TaskSet交给TaskScheduler调度。
- TaskScheduler负责Task级的调度,将DAGScheduler传过来的TaskSet按照指定的调度策略分发到Executor上执行。
- 调度过程中SchedulerBackend负责提供可用资源,其中SchedulerBackend有多种实现,分别对接不同的资源管理系统。SchedulerBackend通过ApplicationMaster申请资源,并不断从TaskScheduler中拿到合适的Task分发到Executor执行。
- HeartbeatReceiver负责接收Executor的心跳信息,监控Executor的存活状况,并通知到TaskScheduler。
广播变量
描述
广播变量用来高效分发较大的对象(不能过大)。向所有工作节点分别发送一个较大的只读值,以供一个或多个Spark操作使用。
未使用广播变量
使用广播变量
scala
package com.wunaiieq.DAG
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CoreBroadcast {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Broadcast")
val sc = new SparkContext(conf)
//1.定义一个本地的集合变量
val localList = List("张三", "李四", "王五")
//2.将本地集合变量定义为一个广播变量
val broadcast: Broadcast[List[String]] = sc.broadcast(localList)
//3.创建RDD对象
val rddName: RDD[String] = sc.parallelize(List("张三", "关羽", "赵云", "李四", "张三", "刘备", "张飞", "王五"))
//4.自定义一个函数
def filterFunc(name: String): Boolean = {
//5.在使用本地集合变量的地方,从广播变量中取出
val blackNames: List[String] = broadcast.value
//6.判断当前的name是否在blackNames中出现
if(blackNames.contains(name)){
return false//黑名单中出现则过滤掉
}else{
return true//未出现则保留
}
}
val resultRdd: RDD[String] = rddName.filter(filterFunc)
println(resultRdd.collect().mkString(","))
sc.stop()
}
}