Shuffle Map Task运算结果的处理

这个结果的处理,分为两部分,一个是在Executor端是如何直接处理Task的结果的;还有就是Driver端,如果在接到Task运行结束的消息时,如何对Shuffle Write的结果进行处理,从而在调度下游的Task时,下游的Task可以得到其需要的数据。

Executor端的处理

在解析BasicShuffle Writer时,我们知道ShuffleMap Task在Executor上运行时,最终会调用org.apache.spark.scheduler.ShuffleMapTask的runTask:

 override def runTask(context: TaskContext): MapStatus = {
// 反序列化广播变量taskBinary得到RDD
val ser = SparkEnv.get.closureSerializer.newInstance()
val (rdd, dep) = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])](
ByteBuffer.wrap(taskBinary.value),Thread.currentThread.getContextClassLoader)
//省略一些非核心代码
val manager =SparkEnv.get.shuffleManager //获得Shuffle Manager
//获得Shuffle Writer
writer= manager.getWriter[Any, Any](dep.shuffleHandle, partitionId, context)
//首先调用rdd .iterator,如果该RDD已经cache了或者checkpoint了,那么直接读取
//结果,否则开始计算计算的结果将调用Shuffle Writer写入本地文件系统
writer.write(rdd.iterator(partition,context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]])
// 返回数据的元数据信息,包括location和size
returnwriter.stop(success = true).get

那么这个结果最终是如何处理的呢?特别是下游的Task如何获取这些Shuffle的数据呢?还要从Task是如何开始执行开始讲起。在Worker上接收Task执行命令的是org.apache.spark.executor.CoarseGrainedExecutorBackend。它在接收到LaunchTask的命令后,通过在Driver创建SparkContext时已经创建的org.apache.spark.executor.Executor的实例的launchTask,启动Task: 

 def launchTask(
context:ExecutorBackend, taskId: Long, taskName: String,serializedTask: ByteBuffer) {
val tr = newTaskRunner(context, taskId, taskName, serializedTask)
runningTasks.put(taskId, tr)
threadPool.execute(tr) // 开始在executor中运行
}

最终Task的执行是在org.apache.spark.executor.Executor.TaskRunner#run。

在Executor运行Task时,得到计算结果会存入org.apache.spark.scheduler.DirectTaskResult。

//开始执行Task,最终得到的是org.apache.spark.scheduler.ShuffleMapTask#runTask
//返回的org.apache.spark.scheduler.MapStatus
val value = task.run(taskId.toInt)
val resultSer = env.serializer.newInstance() //获得序列化工具
val valueBytes = resultSer.serialize(value) //序列化结果
//首先将结果直接放入org.apache.spark.scheduler.DirectTaskResult
val directResult = new DirectTaskResult(valueBytes,accumUpdates, task.metrics.orNull)
val ser = env.closureSerializer.newInstance()
val serializedDirectResult = ser.serialize(directResult)//序列化结果
val resultSize = serializedDirectResult.limit //序列化结果的大小

在将结果回传到Driver时,会根据结果的大小有不同的策略:

1)       如果结果大于1GB,那么直接丢弃这个结果。这个是Spark1.2中新加的策略。可以通过spark.driver.maxResultSize来进行设置。

2)       对于“较大”的结果,将其以taskid为key存入org.apache.spark.storage.BlockManager;如果结果不大,那么直接回传给Driver。那么如何判定这个阈值呢?

这里的回传是直接通过akka的消息传递机制。因此这个大小首先不能超过这个机制设置的消息的最大值。这个最大值是通过spark.akka.frameSize设置的,单位是MBytes,默认值是10MB。除此之外,还有200KB的预留空间。因此这个阈值就是conf.getInt("spark.akka.frameSize",10) * 1024 *1024 – 200*1024。

3)       其他的直接通过AKKA回传到Driver。

实现源码解析如下:

     val serializedResult = {
if (maxResultSize > 0 &&resultSize > maxResultSize) {
// 如果结果的大小大于1GB,那么直接丢弃,
// 可以在spark.driver.maxResultSize设置
ser.serialize(newIndirectTaskResult[Any](TaskResultBlockId(taskId),
resultSize))
} else if (resultSize >=akkaFrameSize - AkkaUtils.reservedSizeBytes) {
// 如果不能通过AKKA的消息传递,那么放入BlockManager
// 等待调用者以网络的形式来获取。AKKA的消息的默认大小可以通过
// spark.akka.frameSize来设置,默认10MB。
val blockId =TaskResultBlockId(taskId)
env.blockManager.putBytes(
blockId, serializedDirectResult,StorageLevel.MEMORY_AND_DISK_SER)
ser.serialize(newIndirectTaskResult[Any](blockId, resultSize))
} else {
//结果可以直接回传到Driver
serializedDirectResult
}
}
// 通过AKKA向Driver汇报本次Task的已经完成
execBackend.statusUpdate(taskId,TaskState.FINISHED, serializedResult)

而execBackend是org.apache.spark.executor.ExecutorBackend的一个实例,它实际上是Executor与Driver通信的接口:

private[spark] trait ExecutorBackend {
def statusUpdate(taskId:Long, state: TaskState, data: ByteBuffer)
}

TaskRunner会将Task执行的状态汇报给Driver(org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend.DriverActor)。 而Driver会转给org.apache.spark.scheduler.TaskSchedulerImpl#statusUpdate。

Driver的处理

TaskRunner将Task的执行状态汇报给Driver后,Driver会转给org.apache.spark.scheduler.TaskSchedulerImpl#statusUpdate。而在这里不同的状态有不同的处理:

1)       如果类型是TaskState.FINISHED,那么调用org.apache.spark.scheduler.TaskResultGetter#enqueueSuccessfulTask进行处理。

2)       如果类型是TaskState.FAILED或者TaskState.KILLED或者TaskState.LOST,调用org.apache.spark.scheduler.TaskResultGetter#enqueueFailedTask进行处理。对于TaskState.LOST,还需要将其所在的Executor标记为failed,并且根据更新后的Executor重新调度。

enqueueSuccessfulTask的逻辑也比较简单,就是如果是IndirectTaskResult,那么需要通过blockid来获取结果:sparkEnv.blockManager.getRemoteBytes(blockId);如果是DirectTaskResult,那么结果就无需远程获取了。然后调用

1)       org.apache.spark.scheduler.TaskSchedulerImpl#handleSuccessfulTask

2)       org.apache.spark.scheduler.TaskSetManager#handleSuccessfulTask

3)       org.apache.spark.scheduler.DAGScheduler#taskEnded

4)       org.apache.spark.scheduler.DAGScheduler#eventProcessActor

5)       org.apache.spark.scheduler.DAGScheduler#handleTaskCompletion

进行处理。核心逻辑都在第5个调用栈。

如果task是ShuffleMapTask,那么它需要将结果通过某种机制告诉下游的Stage,以便于其可以作为下游Stage的输入。这个机制是怎么实现的?

实际上,对于ShuffleMapTask来说,其结果实际上是org.apache.spark.scheduler.MapStatus;其序列化后存入了DirectTaskResult或者IndirectTaskResult中。而DAGScheduler#handleTaskCompletion通过下面的方式来获取这个结果:

val status=event.result.asInstanceOf[MapStatus]

通过将这个status注册到org.apache.spark.MapOutputTrackerMaster,就完成了结果处理的漫长过程:

    mapOutputTracker.registerMapOutputs(
stage.shuffleDep.get.shuffleId,
stage.outputLocs.map(list=> if (list.isEmpty) null else list.head).toArray,
changeEpoch = true)

而registerMapOutputs的处理也很简单,以Shuffle ID为key将MapStatus的列表存入带有时间戳的HashMap:TimeStampedHashMap[Int, Array[MapStatus]]()。如果设置了cleanup的函数,那么这个HashMap会将超过一定时间(TTL,Time to Live)的数据清理掉。

如果您喜欢 本文,那么请动一下手指支持以下博客之星的评比吧。非常感谢您的投票。每天可以一票哦。

Spark技术内幕:Shuffle Map Task运算结果的处理的更多相关文章

  1. Spark技术内幕: Shuffle详解(一)

    通过上面一系列文章,我们知道在集群启动时,在Standalone模式下,Worker会向Master注册,使得Master可以感知进而管理整个集群:Master通过借助ZK,可以简单的实现HA:而应用 ...

  2. Spark技术内幕: Shuffle详解(三)

    前两篇文章写了Shuffle Read的一些实现细节.但是要想彻底理清楚这里边的实现逻辑,还是需要更多篇幅的:本篇开始,将按照Job的执行顺序,来讲解Shuffle.即,结果数据(ShuffleMap ...

  3. Spark技术内幕: Shuffle详解(二)

    本文主要关注ShuffledRDD的Shuffle Read是如何从其他的node上读取数据的. 上文讲到了获取如何获取的策略都在org.apache.spark.storage.BlockFetch ...

  4. Spark技术内幕:Sort Based Shuffle实现解析

    在Spark 1.2.0中,Spark Core的一个重要的升级就是将默认的Hash Based Shuffle换成了Sort Based Shuffle,即spark.shuffle.manager ...

  5. Spark技术内幕: Task向Executor提交的源码解析

    在上文<Spark技术内幕:Stage划分及提交源码分析>中,我们分析了Stage的生成和提交.但是Stage的提交,只是DAGScheduler完成了对DAG的划分,生成了一个计算拓扑, ...

  6. Spark技术内幕: Task向Executor提交的源代码解析

    在上文<Spark技术内幕:Stage划分及提交源代码分析>中,我们分析了Stage的生成和提交.可是Stage的提交,仅仅是DAGScheduler完毕了对DAG的划分,生成了一个计算拓 ...

  7. Spark技术内幕:Shuffle的性能调优

    通过上面的架构和源码实现的分析,不难得出Shuffle是Spark Core比较复杂的模块的结论.它也是非常影响性能的操作之一.因此,在这里整理了会影响Shuffle性能的各项配置.尽管大部分的配置项 ...

  8. Spark技术内幕:Shuffle Pluggable框架详解,你怎么开发自己的Shuffle Service?

    首先介绍一下需要实现的接口.框架的类图如图所示(今天CSDN抽风,竟然上传不了图片.如果需要实现新的Shuffle机制,那么需要实现这些接口. 1.1.1  org.apache.spark.shuf ...

  9. Spark技术内幕:Stage划分及提交源码分析

    http://blog.csdn.net/anzhsoft/article/details/39859463 当触发一个RDD的action后,以count为例,调用关系如下: org.apache. ...

随机推荐

  1. 数据结构与算法 —— 链表linked list(04)

    我们在上篇文章里面提到了链表的翻转,给定一个链表,对每两个相邻的节点作交换,并返回头节点,今天的这道题是它的升级版,如下: k个一组翻转链表 给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链 ...

  2. MySql 使用规范推荐

    前言 废话不多说-- 一.基础规范 1.使用InnoDB存储引擎 支持事务.行级锁.并发性能更好.CPU及内存缓存页优化使得资源利用率更高 2.推荐使用utf8mb4字符集 无需转码,无乱码风险, 支 ...

  3. 规约模式(Specification Pattern)

    一.引言 最近在看一个项目的源码时(DDD),对里面的一些设计思想和设计思路有了一些疑问.当看到(Repository层)中使用了 spec.SatisfiedBy() 时,感觉有点懵.于是在项目中搜 ...

  4. [SPOJ 4155]OTOCI

    Description 题库链接 给你 \(n\) 个节点,让你兹磁以下操作,维护一棵树: 动态加边: 修改点权: 询问路径上点权和. \(1\leq n\leq 30000\) Solution 好 ...

  5. [SCOI 2010]传送带

    Description 题库链接 在一个 \(2\) 维平面上有两条传送带,每一条传送带可以看成是一条线段.两条传送带分别为线段 \(AB\) 和线段 \(CD\) .在 \(AB\) 上的移动速度为 ...

  6. [USACO17FEB]Why Did the Cow Cross the Road I S

    题目描述 Farmer John's cows are trying to learn to cross the road effectively. Remembering the old " ...

  7. [Russian Code Cup 2017 - Finals [Unofficial Mirror]]简要题解

    来自FallDream的博客,未经允许,请勿转载,谢谢. Div1难度+ACM赛制  和几个大佬组队逛了逛 A.给一个大小为n的集合ai(1<=ai<=1000000),要求你构造一个大小 ...

  8. 勤拂拭软件系列教程 - java web开发

    勤拂拭软件工作室持续推出Java Web开发系列教程与案例,供广大朋友分享交流技术经验,帮助喜欢java的朋友们学习进步: 1. 勤拂拭软件 java web 开发教程(1) - 开发环境搭建 勤拂拭 ...

  9. IDE、SDK、API

    IDE 集成开发环境(IDE,Integrated Development Environment )是用于提供程序开发环境的应用程序,一般包括代码编辑器.编译器.调试器和图形用户界面等工具.集成了代 ...

  10. LAN、WAN、WLAN、WiFi之间的区别

    感觉这几个概念让人傻傻分不清,下面以最常见的路由器来解释这几个概念. LAN   1 LAN,全称Local Area Network,中文名叫做局域网. 顾名思义,LAN是指在某一区域内由多台计算机 ...