TaskScheduler内幕天机解密

来源:互联网 发布:总管家软件 编辑:程序博客网 时间:2024/04/30 22:55

一:通过Spark-shell运行程序来观察TaskScheduler内幕

1,当我们启动Spark-shell本身的时候命令终端反馈回来的主要是ClientEndpointSparkDeploySchedulerBackend,这是因为此时还没有任何Job的触发,这是启动Application本身而已,所以主要就是实例化SparkContext并注册当前的应用程序给Master且从集群中获得ExecutorBackend计算资源;

2DAGScheduler划分好Stage后会通过TaskSchedulerImpl中的TaskSetManager来管理当前要运行的Stage中的所有任务TaskSetTaskSetManager会根据locality aware来为Task分配计算资源、监控Task的执行状态(例如重试、慢任务进行推测式执行等)

 

二:TaskSchedulerSchedulerBackend

1, 总体的底层任务调度的过程如下:

a) TaskSchedulerImpl.submitTasks:主要的作用是将TaskSet加入到TaskSetManager中进行管理;

b) SchedulableBuilder.addTaskSetManagerSchedulableBuilder会确定TaskSetManager的调度顺序,然后按照TaskSetManagerlocality aware来确定每个Task具体运行在哪个ExecutorBackend中;

c) CoarseGrainedSchedulerBackend.reviveOffers:DriverEndpoint发送ReviveOffersReviveOffers本身是一个空的case object对象,只是起到触发底层资源调度的作用,在有Task提交或者计算资源变动的时候会发送ReviveOffers这个消息作为触发器;

d) DriverEndpoint接受ReviveOffers消息并路由到makeOffers具体的方法中:在makeOffers方法中首先准备好所有可以用于计算的workOffers(代表了所有可用ExecutorBackend中可以使用的Cores等信息)

e) TaskSchedulerImpl.resourceOffers:为每一个Task具体分配计算资源,输入是ExecutorBackend及其上可用的Cores,输出TaskDescription的二维数组,在其中确定了每个Task具体运行在哪个ExecutorBackendresourceOffers到底是如何确定Task具体运行在哪个ExecutorBackend上的呢?算法的实现具体如下:

i. 通过Random.shuffle方法重新洗牌所有的计算资源以寻求计算的负载均衡;

ii. 根据每个ExecutorBackendcores的个数声明类型为TaskDescriptionArrayBuffer数组;

iii. 如果有新的ExecutorBackend分配给我们的Job,此时会调用executorAdded来获得最新的完整的可用计算计算资源;

iv. 通过下述代码最求最高级别的优先级本地性:

for(taskSet <- sortedTaskSets;maxLocality <- taskSet.myLocalityLevels) {
  do{
    launchedTask = resourceOfferSingleTaskSet(
        taskSet,maxLocality,shuffledOffers,availableCpus,tasks)
  } while(launchedTask)
}

 

v. 通过调用TaskSetManagerresourceOffer最终确定每个Task具体运行在哪个ExecutorBackend的具体的Locality Level

f)通过launchTasks把任务发送给ExecutorBackend去执行;

 

 

 

 

 

 

补充:

1, Task默认的最大重试次数是4次:

def this(sc: SparkContext) =this(sc,sc.conf.getInt("spark.task.maxFailures",4))

 

2, Spark应用程序目前支持两种调度器:FIFOFAIR,可以通过spark-env.shspark.scheduler.mode进行具体的设置,默认情况下是FIFO的方式:

private valschedulingModeConf = conf.get("spark.scheduler.mode","FIFO")
val schedulingMode:SchedulingMode =try {
  SchedulingMode.withName(schedulingModeConf.toUpperCase)

3, TaskScheduler中要负责为Task分配计算资源:此时程序已经具备集群中的计算资源了,根据计算本地性原则确定Task具体要运行在哪个ExecutorBackend中;

4, TaskDescription中已经确定好了Task具体要运行在哪个ExecutorBackend上:

private[spark]class TaskDescription(
    valtaskId: Long,
    valattemptNumber: Int,
    valexecutorId: String,
    valname: String,
    valindex: Int,    // Index within this task's TaskSet
    _serializedTask: ByteBuffer)
  extendsSerializable {

 

而确定Task具体运行在哪个ExecutorBackend上的算法是由TaskSetManagerresourceOffer方法决定

 

5, 数据本地优先级从高到底以此为:优先级高低排: PROCESS_LOCAL, NODE_LOCAL, NO_PREF, RACK_LOCAL, ANY,其中NO_PREF是指机器本地性

6, 每个Task默认是采用一个线程进行计算的:

// CPUs to request per task
val CPUS_PER_TASK= conf.getInt("spark.task.cpus",1)

 

7, DAGScheduler是从数据层面考虑preferedLocation的,而TaskScheduler是从具体计算Task角度考虑计算的本地性;

8, Task进行广播时候的AkkFrameSize大小是128MB,如果任务大于等于128MB-200K的话则Task会直接被丢弃掉;如果小于128MB-200K的话会通过CoarseGrainedSchedulerBackendlaunchTask到具体的ExecutorBackend上;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0 0
原创粉丝点击