我们以sparkPI的样例,我们发现reduce是一个action操作,map, parallelize是transation操作。生成两个两个RDD, MapPartitionsRDD, ParallelCollectionRDD。程序的入口从reduce函数开始。
RDD.reduce
我们知道map, parallelize全部是transaction操作,对应的方法体是new一个新的RDD出来,而reduce是action操作,是使用上一个RDD的实例做对应的操作,所以当前this.rdd=MapPartitionsRDD
在runJob()中,最重要的是调用dagScheduler生成DAG调度
RDD结构
- 一组分片(Partition)
- 分区计算逻辑 compute函数
- 上游依赖列表 在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算
- 分片函数 (默认两种HashPartitioner, RangePartitioner),只有对于于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量
- **存储存取每个Partition的优先位置 ** Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置
ParallelCollectionRDD
PairRDDFunctions.groupByKey
PairRDDFunctions 主要处理k-v类型的类。
ShuffledRDD
shuffledRDD 不同的是有自己每个partition的存储位置。这些数据存储在driver中。
Partitioner
Partitioner是shuffle过程中key重分区时的策略,即计算key决定k-v属于哪个分区。
有两个实现类:HashPartitioner和RangePartitioner。
具体可参考:https://blog.csdn.net/qq_34842671/article/details/83685179
DagScheduler提交任务
eventLoop是一个事件监听的类,最终调用了dagScheduler.handleJobSubmitted();
job的划分是根据RDD的类型是否是action来的,而stage的划分是根据宽依赖而来的。
handlerJobSubmitted构造方法里面的flinalRDD=MapPartitionsRDD
createResultStage 其实是根据resultRDD,往前回溯,判断是否是宽依赖, 是就组装成ShuffleStage, 最终形成一棵依赖树Stage. 并且根据ResultStage 创建job.
getShuffleDependencies 这个方法只是根据一个rdd返回这个rdd所在的宽依赖 ShuffleDependency
真正创建stage的函数是getOrCreateShuffleMapStage, 里面的逻辑大致是先获取当前rdd的shuffleDependency列表,然后创建ShuffleMapRDD, 并且循环递归RDD的依赖。直到全部创建完成。
创建ShuffleMapStage
在这个地方有一个点就是当前RDD的分区数决定了它的并发数。而child的并发数由parent的shuffle并发数决定。
提交Stage
在handleJobSubmitted()中,所有的stage,job树已经组装完毕之后,就要调用submitStage()提交stage了. 提交stage的实质是提交stage分区的task. 在提交之前判断parentStage是否已经提交,没有提交,先递归调用parentStage。最终走到submitMissingTasks()