Spark 源码分析 -- task实际执行过程
Spark源码分析 – SparkContext 中的例子, 只分析到sc.runJob
那么最终是怎么执行的? 通过DAGScheduler切分成Stage, 封装成taskset, 提交给TaskScheduler, 然后等待调度, 最终到Executor上执行
val sc = new SparkContext(……)
val textFile = sc.textFile("README.md")
textFile.filter(line => line.contains("Spark")).count()
这是一个比较简单的没有shuffle的例子, 看看在Executor上是如何被执行的
首先这个job只有一个stage, 所以只会产生resultTask
最关键的执行语句,
func(context, rdd.iterator(split, context))
对于这个例子, func就是最终产生结果的count(), 而rdd就是count前最后一个rdd, 即filter产生的rdd
可以看到Spark中rdd的执行, 不是从前往后, 而是从后往前推的, 为什么? 因为需要考虑cache和checkpoint
所以对于stage只会保留最后一个rdd, 其他的rdd通过dep去反推, 这里调用rdd.iterator来读取最后一个rdd
我可以说iterator是spark中最为核心的一个function吗:-)
final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
if (storageLevel != StorageLevel.NONE) {
SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel)
} else {
computeOrReadCheckpoint(split, context)
}
}
如果结果被cache在memory或disk中, 则调用cacheManager.getOrCompute来读取, 否则直接从checkpoint读或compute
通过CacheManager来完成从cache中读取数据, 或重新compute数据并且完成cache的过程
private[spark] class CacheManager(blockManager: BlockManager) extends Logging {
private val loading = new HashSet[String]
/** Gets or computes an RDD split. Used by RDD.iterator() when an RDD is cached. */
def getOrCompute[T](rdd: RDD[T], split: Partition, context: TaskContext, storageLevel: StorageLevel)
: Iterator[T] = {
val key = "rdd_%d_%d".format(rdd.id, split.index)
blockManager.get(key) match { // 从blockManager中获取cached值
case Some(cachedValues) => // 从blockManager读到数据, 说明之前cache过, 直接返回即可
// Partition is in cache, so just return its values
return cachedValues.asInstanceOf[Iterator[T]]
case None => // 没有读到数据说明没有cache过,需要重新load(compute或读cp)
// Mark the split as loading (unless someone else marks it first)
loading.synchronized { // 防止多次load相同的rdd, 加锁
if (loading.contains(key)) {
while (loading.contains(key)) {
try {loading.wait()} catch {case _ : Throwable =>} // 如果已经在loading, 只需要wait
}
// See whether someone else has successfully loaded it. The main way this would fail
// is for the RDD-level cache eviction policy if someone else has loaded the same RDD
// partition but we didn't want to make space for it. However, that case is unlikely
// because it's unlikely that two threads would work on the same RDD partition. One
// downside of the current code is that threads wait serially if this does happen.
blockManager.get(key) match {
case Some(values) =>
return values.asInstanceOf[Iterator[T]]
case None =>
logInfo("Whoever was loading " + key + " failed; we'll try it ourselves")
loading.add(key)
}
} else {
loading.add(key) // 记录当前key, 开始loading
}
}
try {
// If we got here, we have to load the split
logInfo("Computing partition " + split) // loading的过程,就是读cp或重新compute
val computedValues = rdd.computeOrReadCheckpoint(split, context) // compute的结果是iterator, 何处遍历产生真实数据?
// Persist the result, so long as the task is not running locally
if (context.runningLocally) { return computedValues }
val elements = new ArrayBuffer[Any]
elements ++= computedValues // ++会触发iterator的遍历产生data放到elements中
blockManager.put(key, elements, storageLevel, true) // 对新产生的数据经行cache, 调用blockManager.put
return elements.iterator.asInstanceOf[Iterator[T]]
} finally {
loading.synchronized {
loading.remove(key)
loading.notifyAll()
}
}
}
}
}
Task执行的结果, 如何传到DAGScheduler
task执行的结果value, 参考Spark 源码分析 -- Task
对于ResultTask是计算的值,比如count值,
对于ShuffleTask为MapStatus(blockManager.blockManagerId, compressedSizes), 其中compressedSizes所有shuffle buckets写到文件中的data size
//TaskRunner
val value = task.run(taskId.toInt)
val result = new TaskResult(value, accumUpdates, task.metrics.getOrElse(null))
context.statusUpdate(taskId, TaskState.FINISHED, serializedResult) //context,StandaloneExecutorBackend //StandaloneExecutorBackend.statusUpdate
driver ! StatusUpdate(executorId, taskId, state, data) //DriverActor.StatusUpdate
scheduler.statusUpdate(taskId, state, data.value) //ClusterScheduler.statusUpdate
var taskSetToUpdate: Option[TaskSetManager] = None
taskSetToUpdate.get.statusUpdate(tid, state, serializedData) //ClusterTaskSetManager.statusUpdate
case TaskState.FINISHED =>
taskFinished(tid, state, serializedData) //ClusterTaskSetManager.taskFinished
val result = ser.deserialize[TaskResult[_]](serializedData)
result.metrics.resultSize = serializedData.limit()
sched.listener.taskEnded(tasks(index), Success, result.value, result.accumUpdates, info, result.metrics)
//tasks = taskSet.tasks
//info为TaskInfo
class TaskInfo(
val taskId: Long,
val index: Int,
val launchTime: Long,
val executorId: String,
val host: String,
val taskLocality: TaskLocality.TaskLocality) //DAGScheduler.taskEnded
override def taskEnded(
task: Task[_],
reason: TaskEndReason,
result: Any,
accumUpdates: Map[Long, Any],
taskInfo: TaskInfo,
taskMetrics: TaskMetrics) {
eventQueue.put(CompletionEvent(task, reason, result, accumUpdates, taskInfo, taskMetrics))
} //DAGScheduler.processEvent
handleTaskCompletion(completion) //DAGScheduler.handleTaskCompletion
......
Spark 源码分析 -- task实际执行过程的更多相关文章
- MyBatis 源码分析 - SQL 的执行过程
* 本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析 ...
- Spark源码分析之Checkpoint的过程
概述 checkpoint 的机制保证了需要访问重复数据的应用 Spark 的DAG执行图可能很庞大,task 中计算链可能会很长,这时如果 task 中途运行出错,那么 task 的整个需要重算非常 ...
- Spark 源码分析 -- Task
Task是介于DAGScheduler和TaskScheduler中间的接口 在DAGScheduler, 需要把DAG中的每个stage的每个partitions封装成task 最终把taskset ...
- 通过前端控制器源码分析springmvc的执行过程
第一步:前端控制器接收请求调用doDiapatch 第二步:前端控制器调用处理器映射器查找 Handler 第三步:调用处理器适配器执行Handler,得到执行结果ModelAndView 第四步:视 ...
- Spark源码分析 – 汇总索引
http://jerryshao.me/categories.html#architecture-ref http://blog.csdn.net/pelick/article/details/172 ...
- Spark源码分析 – DAGScheduler
DAGScheduler的架构其实非常简单, 1. eventQueue, 所有需要DAGScheduler处理的事情都需要往eventQueue中发送event 2. eventLoop Threa ...
- MyBatis 源码分析 - 映射文件解析过程
1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...
- spark 源码分析之二十一 -- Task的执行流程
引言 在上两篇文章 spark 源码分析之十九 -- DAG的生成和Stage的划分 和 spark 源码分析之二十 -- Stage的提交 中剖析了Spark的DAG的生成,Stage的划分以及St ...
- Spark源码分析之八:Task运行(二)
在<Spark源码分析之七:Task运行(一)>一文中,我们详细叙述了Task运行的整体流程,最终Task被传输到Executor上,启动一个对应的TaskRunner线程,并且在线程池中 ...
随机推荐
- sphider 丁廷臣简体中文完美汉化版带蜘蛛搜索引擎程序 v1.3.4
sphider 丁廷臣简体中文完美汉化版带蜘蛛搜索引擎程序 v1.3.4是最官方的新版,免费开源,用官方最新发布原版汉化.未更改任何内核文件. Sphider 是一个完美的带有蜘蛛的搜索引擎程序. S ...
- NGINX + LUA实现复杂的控制
安装lua_nginx_module 模块 lua_nginx_module 可以一步步的安装,也可以直接用淘宝的OpenResty Centos和debian的安装就简单了.. 这里说下freebs ...
- 每日英语:Yahoo's Rally: Made in China
The typical honeymoon doesn't last too long before the hard work of marriage begins. And so it norma ...
- 使用python对mysql主从进行监控,并调用钉钉发送报警信息
1.编写python的监控脚本 A.通过获取mysql库中的状态值来判断这个mysql主从状态是否正常 B.进行两个状态值的判断 C.进行调取钉钉机器人,发送消息 2.设置定时任务进行脚本运行 cro ...
- Unix系统编程()复制文件描述符
Bourne shell的IO重定向语法2>&1,意在通知shell把标准错误(文件描述符2)重定向到标准输出(文件描述符1).因此下列命令将把标准输出和标准错误写入result.log ...
- Linux下protobuf的编译与安装
1.下载源码 首先,从github上下载protobuf的源码,地址:https://github.com/google/protobuf,我选择下载2.5.0版本. 2.编译protobuf 将下载 ...
- 树莓派安装centos 7系统
1,格式化 https://www.sdcard.org/downloads/formatter_4/eula_windows/ 2,烧录,Win32DiskImager https://source ...
- 关于SQL高量问题
一工作今天在用DataTable.Table.Select("字段 like")查询时候老是碰到格式不正确 dtrFoundRow = dtvOOView.Table.Select ...
- 你真的需要一个jQuery插件吗
jQuery的插件提供了一个很好的方法,节省了时间和简化了开发,避免程序员从头开始编写每个组件.但是,插件也将一个不稳定因素引入代码中.一个好的插件节省了无数的开发时间,一个质量不好的插件会导致修复错 ...
- 在JAVA中利用public static final的组合方式对常量进行标识
在JAVA中利用public static final的组合方式对常量进行标识(固定格式). 对于在构造方法中利用final进行赋值的时候,此时在构造之前系统设置的默认值相对于构造方法失效. 常量(这 ...