概述

一个Spark的Job分为多个stage,最后一个stage会包含一个或多个ResultTask,前面的stages会包含一个或多个ShuffleMapTasks。

ResultTask运行并将结果返回给driver application。

ShuffleMapTask将task的output依据task的partition分离到多个buckets里。一个ShuffleMapTask相应一个ShuffleDependency的partition,而总partition数同并行度、reduce数目是一致的。

Task

Task的代码在scheduler package下。

抽象类Task构造參数例如以下:

private[spark] abstract class Task[T](val stageId: Int, var partitionId: Int) extends Serializable

Task相应一个stageId和partitionId。

提供runTask()接口、kill()接口等。

提供killed变量、TaskMetrics变量、TaskContext变量等。

除了上述基本接口和变量,Task的伴生对象提供了序列化和反序列化应用依赖的jar包的方法。原因是Task须要保证工作节点具备本次Task须要的其它依赖,注冊到SparkContext下,所以提供了把依赖转成流写入写出的方法。

Task的两种实现

ShuffleMapTask

ShuffleMapTask构造參数例如以下,

private[spark] class ShuffleMapTask(
stageId: Int,
var rdd: RDD[_],
var dep: ShuffleDependency[_,_],
_partitionId: Int,
@transient private var locs: Seq[TaskLocation])
extends Task[MapStatus](stageId, _partitionId)

RDD partitioner相应的是ShuffleDependency。

ShuffleMapTask复写了MapStatus向外读写的方法,由于向外读写的内容包含:stageId,rdd,dep,partitionId,epoch和split(某个partition)。对于当中的stageId,rdd,dep有统一的序列化和反序列化操作并会cache在内存里,再放到ObjectOutput里写出去。序列化操作使用的是Gzip,序列化信息会维护在serializedInfoCache = newHashMap[Int,
Array[Byte]]。这部分须要序列化并保存的原因是:stageId,rdd,dep真正代表了本次Shuffle Task的信息,为了减轻master节点负担,把这部分序列化结果cache了起来。

Stage运行逻辑

主要过程例如以下:

val ser = Serializer.getSerializer(dep.serializer)
shuffle = shuffleBlockManager.forMapTask(dep.shuffleId, partitionId, numOutputSplits, ser)

这一步是初始化一个ShuffleWriterGroup,Group里面是一个BlockObjectWriter数组。

for (elem <- rdd.iterator(split, context)) {
val pair = elem.asInstanceOf[Product2[Any, Any]]
val bucketId = dep.partitioner.getPartition(pair._1)
shuffle.writers(bucketId).write(pair)
}

这一步是为每一个Writer相应一个bucket,调用每一个BlockObjectWriter的write()方法写数据

var totalBytes = 0L
var totalTime = 0L
val compressedSizes: Array[Byte] =
shuffle.writers.map { writer: BlockObjectWriter =>
writer.commit()
writer.close()
val size = writer.fileSegment().length
totalBytes += size
totalTime += writer.timeWriting()
MapOutputTracker.compressSize(size)
}

这一步是运行writer.commit(),并得到结果file segment大小,对总大小压缩

val shuffleMetrics = new ShuffleWriteMetrics
shuffleMetrics.shuffleBytesWritten = totalBytes
shuffleMetrics.shuffleWriteTime = totalTime
metrics.get.shuffleWriteMetrics = Some(shuffleMetrics) success = true
new MapStatus(blockManager.blockManagerId, compressedSizes)

这一步是记录metrcis信息,最后返回一个MapStatus类,里面是本地ShuffleMapTask结果的相关信息。

最后会release writers,让相应的shuffle文件得到记录和重用(ShuffleBlockManager管理这些file,这些file是Shuffle Task中一组Writer写的对象)。

主要把下图看懂。

重要类

介绍涉及到的重要外部类,帮助理解。

ShuffleBlockManager

总体梳理:

ShuffleState维护了两个ShuffleFileGroup的ConcurrentLinkedQueue,以记录眼下shuffle的state。

ShuffleState记录了一次shuffle操作的文件组状态,在ShuffleBlockManager内用Map为每一个shuffleId维护了一个ShuffleState。

每一个shuffleId通过forMapTask()方法得到一组writer,即ShuflleWriterGroup。这组里的writers共享一个shuffleId和mapId,可是每一个相应不同的bucketId和file。在为writer分配FileGroup的时候,会从shuffleId相应的shuffle state里先取unusedFileGroup,假设不存在,则在HDFS上新建File。

对于HDFS上的目标file,writer是能够append写的。在新建file的时候,是依据shuffleId和bucket number和一个递增的fileId来创建新的文件的。

ShuffleFileGroup的重用files和记录mapId,index,offset这块似懂非懂。

重要方法:

def forMapTask(shuffleId: Int, mapId: Int, numBuckets: Int, serializer: Serializer) = { new ShuffleWriterGroup {} }

该方法被一个ShuffleMapTask调用,传入了这次shuffle操作的id,mapId是partitionId。Buckects数目等于分区数目。该方法返回的ShuffleWriterGroup里面是一组DiskBlockObjectWriter,每个writer都属于这一次shuffle操作,所以他们有共同的shuffleId,mapId,可是他们相应了不同的bucket,而且各自相应一个file。

在shuffle run里的调用和參数传入:

val ser = Serializer.getSerializer(dep.serializer)
shuffle = shuffleBlockManager.forMapTask(dep.shuffleId, partitionId, numOutputSplits, ser)

shuffleId是由ShuffleDependency获得的全局唯一id,代表本次shuffle任务id

mapId等于partitionId

Bucket数目等于分区数目

产生writers:

Writer类型是DiskBlockObjectWriter,数目等于buckets数目。bufferSize的设置:

conf.getInt("spark.shuffle.file.buffer.kb", 100) * 1024

blockId产生自:

blockId = ShuffleBlockId(shuffleId, mapId, bucketId)

在生成writer的时候调用的是BlockManager的getDiskWriter方法,ShuffleBlockManager初始化的时候绑定BlockManager。

private[spark] class DiskBlockObjectWriter(
blockId: BlockId,
file: File,
serializer: Serializer,
bufferSize: Int,
compressStream: OutputStream => OutputStream,
syncWrites: Boolean)
extends BlockObjectWriter(blockId)

ShuffleFileGroup:私有内部类,相应了一组shuffle files,每一个file相应一个reducer。一个Mapper会分到一个ShuffleFileGroup,把mapper的结果写到这组File里去。

MapStatus

注意到ShuffleMapTask的类型是MapStatus类。MapStatus类是ShuffleMapTask要返回给scheduler的运行结果,包含两个东西:

class MapStatus(var location: BlockManagerId, var compressedSizes: Array[Byte])

前者是run这次task的block manager地址(BlockManagerId是一个类,保存了executorId,host, port, nettyPort),后者是output大小,该值会传给接下来的reduce任务。该size是被MapOutputTracker压缩过的。

MapStatus类提供了两个方法例如以下,ShuffleMapTask进行了复写。

  def writeExternal(out: ObjectOutput) {
location.writeExternal(out)
out.writeInt(compressedSizes.length)
out.write(compressedSizes)
} def readExternal(in: ObjectInput) {
location = BlockManagerId(in)
compressedSizes = new Array[Byte](in.readInt())
in.readFully(compressedSizes)
}

BlockManagerId

BlockManagerId类构造依赖executorId, host, port, nettyPort这些信息。伴生对象维护了一个blockManagerIdCache ,实现为ConcurrentHashMap[BlockManagerId,BlockManagerId]() 。

比方MapStatus的readExternal方法把ObjectInput传入BlockManagerId构造函数的时候,BlockManagerId的apply()方法就会依据ObjectInput取出executorId, host, port,nettyPort信息,把这个BlockManagerIdobj维护到blockManagerIdCache内

ResultTask

构造參数

private[spark] class ResultTask[T, U](
stageId: Int,
var rdd: RDD[T],
var func: (TaskContext, Iterator[T]) => U,
_partitionId: Int,
@transient locs: Seq[TaskLocation],
var outputId: Int)
extends Task[U](stageId, _partitionId) with Externalizable {

ResultTask比較简单,runTask方法调用的是rdd的迭代器:

  override def runTask(context: TaskContext): U = {
metrics = Some(context.taskMetrics)
try {
func(context, rdd.iterator(split, context))
} finally {
context.executeOnCompleteCallbacks()
}
}

进程模型 vs. 线程模型

Spark同节点上的任务以多线程的方式执行在一个JVM进程中。

长处:

启动任务快

共享内存,适合内存密集型任务

Executor所占资源可反复利用

缺点:

同节点上的全部任务执行在一个进程中,会出现严重的资源争用,难以细粒度控制每一个任务的占用资源。MapReduce为Map Task和Reduce Task设置不同资源,细粒度控制任务占用资源量。

MapReduce的每一个Task都是一个JVM进程,都要经历:资源申请->执行任务->释放资源的过程

每一个节点能够有一个或多个Executor,Executor配有一定数量slots,Executor内能够跑多个Result Task和ShuffleMap Task。

在共享内存方面,broadcast的变量会在每一个executor里存一份,这个executor内的任务能够共享。



全文完 :)

Spark Core源代码分析: Spark任务模型的更多相关文章

  1. Spark Core源代码分析: Spark任务运行模型

    DAGScheduler 面向stage的调度层,为job生成以stage组成的DAG,提交TaskSet给TaskScheduler运行. 每个Stage内,都是独立的tasks,他们共同运行同一个 ...

  2. Spark Core源代码分析: RDD基础

    RDD RDD初始參数:上下文和一组依赖 abstract class RDD[T: ClassTag]( @transient private var sc: SparkContext, @tran ...

  3. Spark SQL 源代码分析之 In-Memory Columnar Storage 之 in-memory query

    /** Spark SQL源代码分析系列文章*/ 前面讲到了Spark SQL In-Memory Columnar Storage的存储结构是基于列存储的. 那么基于以上存储结构,我们查询cache ...

  4. Spark SQL 源代码分析系列

    从决定写Spark SQL文章的源代码分析,到现在一个月的时间,一个又一个几乎相同的结束很快,在这里也做了一个综合指数,方便阅读,下面是读取顺序 :) 第一章 Spark SQL源代码分析之核心流程 ...

  5. Spark SQL源代码分析之核心流程

    /** Spark SQL源代码分析系列文章*/ 自从去年Spark Submit 2013 Michael Armbrust分享了他的Catalyst,到至今1年多了,Spark SQL的贡献者从几 ...

  6. Spark SQL 源代码分析之Physical Plan 到 RDD的详细实现

    /** Spark SQL源代码分析系列文章*/ 接上一篇文章Spark SQL Catalyst源代码分析之Physical Plan.本文将介绍Physical Plan的toRDD的详细实现细节 ...

  7. Spark Core Runtime分析: DAGScheduler, TaskScheduler, SchedulerBackend

    Spark Runtime里的主要层次分析,梳理Runtime组件和运行流程, DAGScheduler Job=多个stage,Stage=多个同种task, Task分为ShuffleMapTas ...

  8. 【Spark Core】任务运行机制和Task源代码浅析1

    引言 上一小节<TaskScheduler源代码与任务提交原理浅析2>介绍了Driver側将Stage进行划分.依据Executor闲置情况分发任务,终于通过DriverActor向exe ...

  9. Spark SQL Catalyst源代码分析之TreeNode Library

    /** Spark SQL源代码分析系列文章*/ 前几篇文章介绍了Spark SQL的Catalyst的核心执行流程.SqlParser,和Analyzer,本来打算直接写Optimizer的,可是发 ...

随机推荐

  1. Windows Azure 网站上的 WordPress 3.8

     编辑人员注释:本文章由 Windows Azure 网站团队的项目经理 Sunitha Muthukrishna 和 Windows Azure 网站开发人员体验合作伙伴共同撰写. WordPr ...

  2. [Windows编程] 开发DLL必读《Best Practices for Creating DLLs》

    开发DLL的时候,需要十分注意 DllMain 函数,因为在多线程环境下DLLMain里面的代码很容易引发线程死锁. 这篇MSDN文章<Best Practices for Creating D ...

  3. WinRT Toolkit 介绍--Control篇

    WinRT toolkit是组针对Windows Runtime XAML开发的一系列Control,extension和helper类.它和Windows Phone Toolkit一样,也是由很多 ...

  4. C#中字符串的处理,对象的引用及继承(Tenth day)

    又进入到了新的一周,现在到总结的时间了,继续为大家总结一下今天在云和学院所学的知识. 理论: StringBuilder 和 String 的区别    String 在进行运算时(如赋值.拼接等)会 ...

  5. F - 蜘蛛牌(深度搜索)

    Problem Description 蜘蛛牌是windows xp操作系统自带的一款纸牌游戏,游戏规则是这样的:只能将牌拖到比她大一的牌上面(A最小,K最大),如果拖动的牌上有按顺序排好的牌时,那么 ...

  6. python中mcmc方法的实现

    MCMC方法在贝叶斯统计中运用很多,MIT发布的EMCEE是实现的比较好的.介绍页面在下面.源代码中examples里的代码可以帮助理解各种功能,特别是line.py 列出了最小二乘法,最大似然法和M ...

  7. 2013 南京邀请赛 A play the dice 求概率

    /** 大意:给定一个色子,有n个面,每一个面上有一个数字,在其中的m个面上有特殊的颜色,当掷出的色子出现这m个颜色之一时,可以再掷一次..求其最后的期望 思路:假设 期望为ans 4 ans = 1 ...

  8. 数据结构——链表(linkedlist)

    基本分类: 1.单向链表 2.带尾指针的单向链表 3.双向循环链表 以下分类进行说明 1.单向链表 基本元素:*front //头节点 *next //下一节点 声明:node<T>*p; ...

  9. Javabyte[]数组和十六进制String之间的转换Util------包含案例和代码

    Java中byte用二进制表示占用8位,而我们知道16进制的每个字符需要用4位二进制位来表示(23 + 22 + 21 + 20 = 15),所以我们就可以把每个byte转换成两个相应的16进制字符, ...

  10. C# Setup package Uninstaller

    安裝的部分就不介紹了,網上一搜一大堆,這裡只介紹下卸載的部分. 1.在C:\Windows\System32 目录下找到 msiexec.exe 拷贝到相应的地方,可修改名称为 Uninstall.e ...