关于任务调度的文章,网上很多,我是本着想系统深刻理解的目的,写下这边文章
任务调度:
1.Action类型的算子触发job的执行。源码中调用了SparkContext的runJob()方法,跟进源码发现底层调用的是DAGScheduler的runJob()方法。
DAGScheduler会将我们的job按照宽窄依赖划分为ResultStage和ShuffleMapStage,
根据stage类型生成对应的task,分别是ShuffleMapTask、ResultTask最后调用TaskScheduler提交任务,如下图。
2.stage划分完之后会以TaskSet的形式提交给我们的TaskScheduler。
TaskScheduler接收到TaskSet之后会进行遍历,每遍历一条调用launchTask()方法,launchTask()根据数据本地化的算法发送task到指定的Executor中执行。
task在发送到Executor之前首先进行序列化,Executor中有ThreadPool,ThreadPool中有很多线程,在这里面来具体执行我们的task。
3.Executor接收到的task分为ShuffleMapTask和ResultTask
ShuffleMapTask和ResultTask,分别对应shuffle和非shuffle任务。ShuffleMapTask 是一个管道,管道的计算结果会在shuffle write阶段数据落地,数据落地会根据我们的分区策略写入到不同的磁盘小文件中,注意相同的key一定写入到相同的磁盘小文件中),map端执行完成之后,会向Driver中的DAGScheduler对象里面的MapOutputTracker发送了一个ShuffleMapTask的执行状态(成功还是失败还有每一个小文件的地址)。然后ResultTask开始执行,reduce端的输入数据就是map端的输出数据。那么如何拿到map端的输出数据呢?ResultTask会先向Driver中MapOutPutTracker请求这一批磁盘小文件的地址,拿到地址后,由ResultTask所在的Executor里面的BlockManager向ShuffleMapTask 所在的Executor先建立连接,连接是由ConnectionManager负责的,然后由BlockTransformService去拉取数据,拉取到的数据作为ResultTask输入数据。
对计算结果进行序列化,再根据其大小采取相应方式处理,最后调用CoarseGrainedExecutorBackend的statusUpdate方法返回result给Driver。