rdd 依赖
依赖关系
相邻的两个rdd之间的关系称之为依赖关系
多个连续的rdd的依赖关系称之为血缘关系(lineage)
- 宽依赖
同一个上游rdd 的partition 被多个下游 rdd 的 partition 依赖会引起 shuffle
阶段划分
任务的阶段划分
源码
DAGScheduler.submitJob -->
eventProcessLoop = new DAGSchedulerEventProcessLoop
eventProcessLoop.post(JobSubmitted(
jobId, rdd, func2, partitions.toArray, callSite, waiter,
Utils.cloneProperties(properties)))
DAGSchedulerEventProcessLoop.onReceive dag 调度中主事件循环
==>DAGSchedulerEventProcessLoop.doOnReceive
==>{
case JobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties) =>
dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties)
}
==>
==>DAGScheduler.handleJobSubmitted 方法
==> finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
val parents = getOrCreateParentStages(shuffleDeps, jobId) ==>
{
shuffleDeps.map { shuffleDep =>
getOrCreateShuffleMapStage(shuffleDep, firstJobId)
}.toList ==>{
createShuffleMapStage(shuffleDep, firstJobId) ==>{
val stage = new ShuffleMapStage(id, rdd, numTasks, parents, jobId, rdd.creationSite, shuffleDep, mapOutputTracker,
resourceProfile.id)
}
}
}
rdd 任务划分
rdd 的任务切分中间为 Application ,Job,Stage 和 Task
- Application: 初始化一个 sparkContext 即 会生成一个 Application
- Job: 一个算子就会生成一个 job
- Stage: Satge个数等于宽依赖(shuffleDependency)的个数加1
- Task:一个Stage 中最后一个Rdd 分区的个数就是 Task的个数
Application -> Job -> Stage ->Task 每层都是1对n 的关系
源码:
val tasks: Seq[Task[_]] = {
val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
stage match {
case stage: ShuffleMapStage =>
stage.pendingPartitions.clear()
partitionsToCompute.map { id =>
val locs = taskIdToLocations(id)
val part = partitions(id)
stage.pendingPartitions += id
new ShuffleMapTask(stage.id, stage.latestInfo.attemptNumber,
taskBinary, part, locs, properties, serializedTaskMetrics, Option(jobId),
Option(sc.applicationId), sc.applicationAttemptId, stage.rdd.isBarrier())
}
case stage: ResultStage =>
partitionsToCompute.map { id =>
val p: Int = stage.partitions(id)
val part = partitions(p)
val locs = taskIdToLocations(id)
new ResultTask(stage.id, stage.latestInfo.attemptNumber,
taskBinary, part, locs, id, properties, serializedTaskMetrics,
Option(jobId), Option(sc.applicationId), sc.applicationAttemptId,
stage.rdd.isBarrier())
}
}
}
==> val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
==>ResultStage.findMissingPartitions(): Seq[Int] = {
val job = activeJob.get
(0 until job.numPartitions).filter(id => !job.finished(id))
}
rdd 的 持久化操作
-
由于 rdd 不保存数据 ,所以 当有重复使用同一个rdd的场景时 spark 会 根据 rdd 的依赖逻辑从头计算一遍,这样执行效率不高
所以为了便于rdd 的 复用增加任务执行的效率 ,可以对复用的rdd 进行持久化操作 -
对于 依赖链路 较长 的 rdd 也可以进行持久化操作
-
会在血缘关系中添加新的依赖,一旦出现问题 可以从头读取数据
// cache 方法 默认将 数据保存在内存中
rdd.cache()
// persist 方法可以指定持久化的 保存方法 ,保存到 disk 的 文件为临时文件 在任务结束时会被删除
rdd.persist(StorageLevel.MEMORY_AND_DISK)
checkpoint 操作
checkpoint需要落盘,所以需要指定检查点保存路径,
检查点路径保存的文件,任务结束后 不会被删除
checkpoint 的调用会产生一个独立的Task 去执行检查点落盘的操作,为了提高效率 一般需要和cache() 联合使用
- checkpoint 执行 会切断血缘关系,从新建立血缘关系,相当于读取新的数据源
//设置检查点保存路径
sc.setCheckpointDir("/")
//设置检查点
map.checkpoint()
自定义分区器
继承 Partitioner 类,重写方法
def main(args: Array[String]): Unit = {
var sparkConf = new SparkConf().setMaster("local[*]").setAppName("UserdefinePartitioner")
var sc = new SparkContext(sparkConf)
val value = sc.makeRDD(List(("a",3),("b", 2),("c",1) ,("a",10),("d",5),("f",20)))
val partRdd = value.partitionBy(new MyPartitioner)
partRdd.saveAsTextFile("output")
sc.stop()
}
class MyPartitioner extends Partitioner{
// 分区数量
override def numPartitions: Int = 3
//根据数据的key值返回数据的分区索引 (从0 开始)
override def getPartition(key: Any): Int = {
key match {
case "a"=>0
case "b"=>1
case _=>2
}
}
}