Spark源码分析 -- TaskScheduler
Spark在设计上将DAGScheduler和TaskScheduler完全解耦合, 所以在资源管理和task调度上可以有更多的方案
现在支持, LocalSheduler, ClusterScheduler, MesosScheduler, YarnClusterScheduler
先分析ClusterScheduler, 即standalone的Spark集群上, 因为比较单纯不涉及其他的系统, 看看Spark的任务是如何被执行的
private var taskScheduler: TaskScheduler = {
case SPARK_REGEX(sparkUrl) =>
val scheduler = new ClusterScheduler(this) // 创建ClusterScheduler
val backend = new SparkDeploySchedulerBackend(scheduler, this, sparkUrl, appName) // 创建SparkDeploySchedulerBackend
scheduler.initialize(backend)
scheduler
}
TaskScheduler接口, 注释写的非常清楚
/**
* Low-level task scheduler interface, implemented by both ClusterScheduler and LocalScheduler.
* These schedulers get sets of tasks submitted to them from the DAGScheduler for each stage,
* and are responsible for sending the tasks to the cluster, running them, retrying if there
* are failures, and mitigating stragglers. They return events to the DAGScheduler through
* the TaskSchedulerListener interface.
*/
private[spark] trait TaskScheduler {
def rootPool: Pool
def schedulingMode: SchedulingMode
def start(): Unit // 启动
def postStartHook() { }
def stop(): Unit
// Submit a sequence of tasks to run.
def submitTasks(taskSet: TaskSet): Unit // 核心, 提交taskset的接口
// Set a listener for upcalls. This is guaranteed to be set before submitTasks is called.
def setListener(listener: TaskSchedulerListener): Unit // TaskScheduler会使用这个listener来汇报当前task的运行状况,会注册DAGScheduler
// Get the default level of parallelism to use in the cluster, as a hint for sizing jobs.
def defaultParallelism(): Int
}
ClusterScheduler
对于集群的TaskScheduler实现, 相对于LocalScheduler
主要就是创建和管理schedulable tree, 参考Spark源码分析 – SchedulableBuilder
当然最终和cluster的executor通信还是需要依赖SparkDeploySchedulerBackend, 参考Spark源码分析 – SchedulerBackend
对于submitTasks,
首先将tasksetmanager放入schedulable tree等待schedule (delay schedule, 不一定会马上被调度到)
然后给SchedulerBackend发送reviveOffers event, 请求分配资源并launch tasks (launch的并一定是刚提交的tasks)
SchedulerBackend会向cluster申请workOffers(对于standalonebackend, 这步省略了), 然后再调用ClusterScheduler.resourceOffers来根据可用的workOffers分配tasks
最终给executors发送LaunchTask, 启动tasks
resourceOffers是核心函数, 当得到可用的workerOffer后, 用于从schedulable tree中schedule合适的被执行的tasks
resourceOffers的逻辑有点小复杂
1. 首先依次遍历sortedTaskSets, 并对于每个Taskset, 遍历TaskLocality
2. 越local越优先, 找不到(launchedTask为false)才会到下个locality级别
3. 在多次遍历offer list, 因为一次taskSet.resourceOffer只会占用一个core, 而不是一次用光所有的core, 这样有助于一个taskset中的task比较均匀的分布在workers上
4. 只有在该taskset, 该locality下, 对所有worker offer都找不到合适的task时, 才跳到下个locality级别
private[spark] class ClusterScheduler(val sc: SparkContext) extends TaskScheduler with Logging
{
var listener: TaskSchedulerListener = null
var backend: SchedulerBackend = null
val mapOutputTracker = SparkEnv.get.mapOutputTracker
var schedulableBuilder: SchedulableBuilder = null
var rootPool: Pool = null
// default scheduler is FIFO
val schedulingMode: SchedulingMode = SchedulingMode.withName(
System.getProperty("spark.scheduler.mode", "FIFO"))
def initialize(context: SchedulerBackend) {
backend = context // 初始化SchedulerBackend
// temporarily set rootPool name to empty
rootPool = new Pool("", schedulingMode, 0, 0) // 创建Schedulable tree的root pool
schedulableBuilder = { // 用schedulableBuilder初始化Schedulable tree
schedulingMode match {
case SchedulingMode.FIFO =>
new FIFOSchedulableBuilder(rootPool)
case SchedulingMode.FAIR =>
new FairSchedulableBuilder(rootPool)
}
}
schedulableBuilder.buildPools()
}
override def start() {
backend.start() // 启动SchedulerBackend
}
override def submitTasks(taskSet: TaskSet) {
val tasks = taskSet.tasks
logInfo("Adding task set " + taskSet.id + " with " + tasks.length + " tasks")
this.synchronized {
val manager = new ClusterTaskSetManager(this, taskSet)
activeTaskSets(taskSet.id) = manager
schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties) // 将TaskSetManager加到Schedulable tree等待被调度执行
taskSetTaskIds(taskSet.id) = new util.HashSet[Long]()
backend.reviveOffers() // 调用SchedulerBackend的reviveOffers, 其实就是往DriverActor发送reviveOffers事件
}
/**
* Called by cluster manager to offer resources on slaves. We respond by asking our active task
* sets for tasks in order of priority. We fill each node with tasks in a round-robin manner so
* that tasks are balanced across the cluster.
*/
// 根据当前可用的worker offers, 分配tasks
def resourceOffers(offers: Seq[WorkerOffer]): Seq[Seq[TaskDescription]] = synchronized {
SparkEnv.set(sc.env) // Build a list of tasks to assign to each worker
val tasks = offers.map(o => new ArrayBuffer[TaskDescription](o.cores)) // 每个core可以分配一个task,所以对每个offer生成length为cores数目的ArrayBuffer
val availableCpus = offers.map(o => o.cores).toArray // 每个work可用的core数目的array
val sortedTaskSets = rootPool.getSortedTaskSetQueue() // 得到根据schedule算法排序后的TaskSetManager列表
// Take each TaskSet in our scheduling order, and then offer it each node in increasing order
// of locality levels so that it gets a chance to launch local tasks on all of them.
var launchedTask = false
for (taskSet <- sortedTaskSets; maxLocality <- TaskLocality.values) { // 嵌套, 遍历sortedTaskSets, 并对每个taskSet遍历所有TaskLocality
do {
launchedTask = false
for (i <- 0 until offers.size) { // 遍历每个offer, 试图在当前的taskset和当前的locality上找到合适的task
val execId = offers(i).executorId
val host = offers(i).host
for (task <- taskSet.resourceOffer(execId, host, availableCpus(i), maxLocality)) { // 每次只会返回最多一个task
tasks(i) += task
val tid = task.taskId
taskIdToTaskSetId(tid) = taskSet.taskSet.id
taskSetTaskIds(taskSet.taskSet.id) += tid
taskIdToExecutorId(tid) = execId
activeExecutorIds += execId
executorsByHost(host) += execId
availableCpus(i) –= 1 // 分配一个task, 所以availableCpus - 1
launchedTask = true
}
}
} while (launchedTask) // 找到,就继续在这个locality上找task, 否则放宽到下个locality,或下个taskset
} if (tasks.size > 0) {
hasLaunchedTask = true
}
return tasks
}
}
Spark源码分析 -- TaskScheduler的更多相关文章
- Spark源码分析 – 汇总索引
http://jerryshao.me/categories.html#architecture-ref http://blog.csdn.net/pelick/article/details/172 ...
- Spark源码分析(三)-TaskScheduler创建
原创文章,转载请注明: 转载自http://www.cnblogs.com/tovin/p/3879151.html 在SparkContext创建过程中会调用createTaskScheduler函 ...
- spark 源码分析之四 -- TaskScheduler的创建和启动过程
在 spark 源码分析之二 -- SparkContext 的初始化过程 中,第 14 步 和 16 步分别描述了 TaskScheduler的 初始化 和 启动过程. 话分两头,先说 TaskSc ...
- Spark源码分析:多种部署方式之间的区别与联系(转)
原文链接:Spark源码分析:多种部署方式之间的区别与联系(1) 从官方的文档我们可以知道,Spark的部署方式有很多种:local.Standalone.Mesos.YARN.....不同部署方式的 ...
- Spark 源码分析 -- task实际执行过程
Spark源码分析 – SparkContext 中的例子, 只分析到sc.runJob 那么最终是怎么执行的? 通过DAGScheduler切分成Stage, 封装成taskset, 提交给Task ...
- Spark源码分析 – SchedulerBackend
SchedulerBackend, 两个任务, 申请资源和task执行和管理 对于SparkDeploySchedulerBackend, 基于actor模式, 主要就是启动和管理两个actor De ...
- Spark源码分析 – DAGScheduler
DAGScheduler的架构其实非常简单, 1. eventQueue, 所有需要DAGScheduler处理的事情都需要往eventQueue中发送event 2. eventLoop Threa ...
- Spark源码分析 – SparkContext
Spark源码分析之-scheduler模块 这位写的非常好, 让我对Spark的源码分析, 变的轻松了许多 这里自己再梳理一遍 先看一个简单的spark操作, val sc = new SparkC ...
- Spark源码分析之七:Task运行(一)
在Task调度相关的两篇文章<Spark源码分析之五:Task调度(一)>与<Spark源码分析之六:Task调度(二)>中,我们大致了解了Task调度相关的主要逻辑,并且在T ...
随机推荐
- 一款纯html5实现的时钟
今天给大家分享一款非常漂亮的纯html5实现的时钟.整个界面都由html5绘制而成.一起看下效果图: 在线预览 源码下载 实现的代码. html代码: <div class="co ...
- Unix系统编程()进程内存布局
每个进程所分配的内存由很多部分组成,通常称之为"段(segment)". 文本段包含了进程运行的程序机器语言指令.文本段具有只读属性,以防止进程通过错误指针意外修改自身指令. 因为 ...
- [Eth]Mac/Phy/mdio/Rgmii
转自:http://www.cnblogs.com/zxc2man/p/3769777.html 1. MDIO(Management Data Input/Output),对G比特以太网而言,串行通 ...
- 去掉Scala的糖衣(4) -- Type Aliase
我的新博客地址:http://cuipengfei.me/blog/2013/12/23/desugar-scala-4/ Scala中有一个type关键字,用来给类型或者是操作起别名,用起来很是方便 ...
- phpstorm 中文版 支持BUG调试 IDE
下载地址:http://dx2.7down.net/soft/P/phpstorm8_cn.zip
- 数据库 Oracle数据库对象二
视图 --视图是对表逻辑抽象 --视图的好处:简化查询 --视图是一种虚表 --视图建立在已有表的基础上,视图赖以建立的这些吧称为基表. --向视图提供数据内容的语句为select语句,可以将视图理解 ...
- 使用PULL解析XML文件
转载博文1:http://blog.csdn.net/wangkuifeng0118/article/details/7313241 XmlPull和Sax类似,是基于流(stream)操作文件,然后 ...
- Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的云应用开发工具:Spr ...
- Unity Shaders and Effects Cookbook (3-5) 金属软高光
书上这一节看得我头昏脑胀,数学渣表示自理不能-- 并且也不了解这个效果的实际意义. 先记录下来,后面真正看懂了再来补充具体理论. 通过一张纹理贴图,定义高光的形状,利用到的纹理贴图有三种 这里并非把纹 ...
- CImage类提供了GetBits()函数原理及实现
CImage类提供了GetBits()函数来读取数据区,GetBits()函数返回的是图片最后一行第一个像素的地址,网上有人说返回指针的起始位置是不同的,有些图片返回的是左上角像素的地址,有些是左下角 ...