Spark中多个任务间的调度逻辑

    本文想要了解一个问题,如果多个线程通过同一个SparkSession提交作业,不同线程间的作业是怎么调度的,工程中Spark使用的是FIFO模式。

    单个Spark作业详细的运行流程可见之前写的那篇文章《Spark-Job执行流程分析》。这里简单提一下,一个action操作会被DAGScheduler根据Shuffle关系拆分成多个stage,同一个action中的stage是串行执行的。每个stage会创建一批task,称为TaskSet。TaskSet会交给TaskScheduler进行调度,分配到各个Executor进程中执行。TaskSet中的task是最小的计算单元,每个task对应计算一个Partition中的数据。

 

    从DAGSchedule中的taskScheduler.submitTasks(new TaskSet(tasks.toArray, stage.id, stage.latestInfo.attemptId, jobId, properties))这一行开始看进去。该方法最终会将TaskSet封装成TaskSetManager,然后提交到调度池中调度执行:

    然后会Driver端会发送backend.reviveOffers消息执行任务,任务会经历如下三个方法:

    TaskSchedulerImpl.resourceOffers()选择要执行的任务,给任务资源分配===> resourceOfferSingleTaskSet()根据数据本地性进行单个TaskSet任务资源分配===> TaskSetManager.resourceOffer()分配task执行

 

TaskSetManager之间的调度

    TaskSetManager之间的调度就发生在上面的resourceOffers()方法中,在resourceOfferSingleTaskSet()方法执行之前。之前的文章中有提到过,Spark中有两种调度池:FIFO 和 FAIR。

    FIFO:后提交的TaskSetManager等待先提交的TaskSetManager执行完毕才能执行(当然如果前一个任务CPU核数没有用完的话,多余的CPU可以用来执行后续的任务)。

    FAIR:通过fairscheduler.xml来配置,根据其中的weight、minShare配置项进行任务调度。下图是官方自带FAIR调度池配置的例子:

    minShare:调度池最小资源数(cores,即CPU核数),默认为0

    weight:定的是任务的权重(weight为2的分配到的资源为weight为1的两倍,但是是在满足了minShare之后,多于的CPU资源才会按照weight的比例进行分配)

 

    如果是FIFO模式,那么内部只有一个pool。如果是FAIR模式,内部可以将任务指定提交到不同的Pool中,实现不同Pool之间的CPU资源隔离。可以通过参数spark.scheduler.allocation.file设置用户自定义配置文件

 

排序算法在SchedulingAlogrithm.scala类中:

    对于FIFO模式,先比较优先级,优先级高(编号小,在FIFO模式下就是jobId)的先运行。优先级相同的情况下再比较stageId,stageId小的先运行。

    所以对于多个Job来说,如果CPU核数不充足的情况下,肯定是先运行完毕其中的一个Job,再运行下一个(从后面单个TaskSet调度的代码中可以看到,CPU核数充足的话,多个Job是可以同时运行的)。

 

 

FAIR计算就比较复杂一点,

    根据runningTask minShare weight三者综合来判断优先级

    对于TaskSetManager1和TaskSetManager2来说

    1.如果其中某一个的runningTask < minShare(最少任务运行数量),比如TaskSetManager1, 那么就先运行TaskSetManager1(任务数少)

    2.如果都满足最少任务运行数量了,那么比较runningTasks/minShare的值,谁小说明使用的资源少,先运行资源少的那个(使用资源占比少)

    3.如果满足资源比较,再比较权重runningTasks/weight的值,权重越高,这个值越小,先让权重高(值小)的先运行

    4.还是不能就只能比较两个stage的名字了(比较stage的Id编号)

    对TaskSetManager排好序之后,接下来就是对SortedTaskSet(TaskSet == TaskSetManager)逐个进行调度了。

    ps:逐个调度并不代表TaskSetManager不能并行执行

 

 

 

TaskSetManager内部的Task调度

结合TaskSet排序以及本地性将Tasks分配给各个Executor:

    按照TaskSet的数据本地性,逐个给TaskSet分配资源,知道所有CPU都分配完毕,然后开始运行Task。即这些待运行的Task不一定是同一个TaskSet的。但是属于同一个TaskSet的task,它们的数据本地性是同一个级别的。如下所示:

 

    Task的本地性分为5个级别:按照PROCESS_LOCAL(本进程)  ->  NODE_LOCAL(本节点)  ->  NO_PREF  ->  RACK_LOCAL -> ANY 的顺序从TaskSetManager中选择Task去这个executor上面执行。

 

 

 

总结:

所以针对开篇提出来的问题可以得出如下结论,使用FIFO模式多线程提交多个job时:

1.总CPU资源大于所有job需要的CPU资源总和,那么多个job是可以完全并行执行的

2.如果总CPU资源小于某个job的CPU资源总和并且这个job满足当前数据本地性的task数量仍大于总CPU数量,那么只能运行一个job

3.如果总CPU资源大于一个job需要的CPU资源数量,小于两个job的CPU数量,那么第一个job的Task可以完全执行,第二个job只能执行部分Task

 

什么情况下会使用到FAIR模式呢?

比如本人当前的项目,所有的任务都是通过同一个SparkSession提交的,有一种任务会运行耗时很久,而其他的任务运行相对较快。

在当前使用FIFO模式的情况下,如果耗时久的任务先运行的话,后续的任务都要等待这个任务运行完毕才能运行,不满足用户要求

如果使用FAIR模式对CPU资源进行隔离,虽然运行慢的任务会变的更慢,但是那些运行快的任务就会获得CPU资源从而快速的运行完毕。

 

 

说明下 粗粒度资源分配 VS 细粒度资源分配:

粗粒度资源申请(Spark):

将所有Application所要用到的资源申请完毕,才会进行任务的调度。当所有的task执行完成后才会释放这部分资源。

优点:由于所有的资源都申请完毕,所以任务运行速度就快了。

缺点:直到最后一个task执行完成才会释放资源,集群的资源无法充分利用。当数据倾斜时更严重。

 

细粒度资源申请(MapReduce):

Application执行之前不需要先去申请资源,而是直接执行,让job中的每一个task在执行前自己去申请资源,task执行完成就释放资源。

优点:集群的资源可以充分利用。

缺点:task自己去申请资源,task启动变慢,任务运行的速度就相应的变慢了

 

 

 

 

 

 

参考:

    https://blog.csdn.net/LINBE_blazers/article/details/92801646(TaskSchedulerImpl对执行结果的处理)

    https://www.cnblogs.com/SysoCjs/p/11466243.html(TaskSet获取时的排序算法)

    https://yq.aliyun.com/articles/680995(Spark中的资源调度)

    https://cloud.tencent.com/developer/article/1004904(Scheduler内部调度原理)

    https://blog.csdn.net/LINBE_blazers/article/details/92396898(单个TaskSet中的调度)

    https://blog.csdn.net/zwgdft/article/details/88349295(Spark并行执行多个job)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值