spark任务调度机制尝试利用RDD的特性来为所有的操作找到一种最有效的执行策略,任务调度器有一个runJob的接口提供给RDD使用,它接受的参数包括RDD本身,感兴趣的数据块部分以及应用于数据块之上的函数。当RDD需要执行一个操作(count, collect, saveAsTextFile, etc)的时候,就会调用runJob函数来在集群之上进行计算。
总体上来说,DPark的任务调度器和Dryad的比较相似,但是DPark的调度器在调度任务时会将RDD的哪个部分被缓存在哪些机器上这个因素考虑在内。首先,调度器会根据最终RDD的血统序列来创建出一些阶段(stage),每个阶段会包含尽可能多的可以被连续运行的变换,即基于窄依赖的变换,一个stage的边界是那些需要在节点之间移动数据的宽依赖变换,或者是那些已经被缓存了的RDD。下图表明了一个整体计算的Stage分割,只有当父阶段完成之后,我们才会启动子阶段的计算,并为每一个数据块的计算分配一个任务,当然没有父子关系的阶段可以被同时运行,就像例子中的阶段一和阶段二。
其次,调度器会根据数据本地化原则来分配任务到对应的节点,以尽可能减少通讯成本。也就是说,如果一个任务需要处理一个已经被缓存的数据块,那调度器就会将这个任务分配到具有这个缓存数据块的节点上进行计算;否则,一个任务会被分配到它所需要处理的数据块的RDD希望它被分配到的节点上去(通过RDD的preferredLocations函数)。
最后,如果某一个任务运行失败了,如果它所在的阶段的父阶段数据没有被丢失,那么调度器会直接将其在另一个节点上重新运行,如果父阶段已经不可用了,那么我们会重新提交父阶段中需要被重新计算的任务。具体请参见schedule.runJob中submitStage和submitMissingStage函数。