1. 启动任务

在前面一篇博客中(Driver 启动、分配、调度Task)介绍了Driver是如何调动、启动任务的,Driver向Executor发送了LaunchTask的消息,Executor接收到了LaunchTask的消息后,进行了任务的启动,在CoarseGrainedExecutorBackend.scala
case LaunchTask(data) =>
if (executor == null) {
exitExecutor(, "Received LaunchTask command but executor was null")
} else {
val taskDesc = ser.deserialize[TaskDescription](data.value)
logInfo("Got assigned task " + taskDesc.taskId)
executor.launchTask(this, taskId = taskDesc.taskId, attemptNumber = taskDesc.attemptNumber,
taskDesc.name, taskDesc.serializedTask)
}
接收消息,反序列化了TaskDescription的对象
 
在TaskDescription反序列化了taskId, executeId, name,index, attemptNumber, serializedTask属性,其中serializedTask是ByteBuffer。
Executor的launchTask方法
def launchTask(
context: ExecutorBackend,
taskId: Long,
attemptNumber: Int,
taskName: String,
serializedTask: ByteBuffer): Unit = {
val tr = new TaskRunner(context, taskId = taskId, attemptNumber = attemptNumber, taskName,
serializedTask)
runningTasks.put(taskId, tr)
threadPool.execute(tr)
}

方法中通过线程池中启动了线程运行TaskRunner的任务

private val threadPool = ThreadUtils.newDaemonCachedThreadPool("Executor task launch worker")  
关于线程池,在executor启动的是一个无固定大小线程数量限制的线程池,也就是说在executor的设计中,启动的任务数量是完全由Driver来管控

2. 任务的运行

前面提到了TaskDescription中的serializedTask是个bytebuffer, 里面的结构如下图所示:
 
分别是task所依赖的文件的数量,文件的名字,时间戳,Jar的数量,Jar的名字,Jar的时间戳,属性,subBuffer是个bytebuffer

2.1 加载Jars文件

Driver所运行的class等包括依赖的Jar文件在Executor上并不存在,Executor首先要fetch所依赖的jars,也就是TaskDescription中serializedTask中的jar部分
在上面的结构描述中,jar相关的只是numJars,jarName,timestamp并没有jar的内容,也就是在LaunchTask里的消息中并不携带Jar的内容,原因也很容易理解,rpc的消息体必须简单高效
  • timestamp:这是用于判断文件的时间戳,在相同文件名的情况下只有新的才需要重新fetch
  • jarName: 这里的JarName是网络文件名:spark://192.168.121.101:37684/jars/spark-examples_2.11-2.1.0.jar
 
通常在相同的Driver在起多个任务的时候,任务的所依赖的jar是基本相同的,所以没必要每个Task都重新fetch相同的jars
for ((name, timestamp) <- newJars) {
val localName = name.split("/").last
val currentTimeStamp = currentJars.get(name)
.orElse(currentJars.get(localName))
.getOrElse(-1L)
if (currentTimeStamp < timestamp) {
logInfo("Fetching " + name + " with timestamp " + timestamp)
// Fetch file with useCache mode, close cache for local mode.
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory()), conf,
env.securityManager, hadoopConf, timestamp, useCache = !isLocal)
currentJars(name) = timestamp
// Add it to our class loader
val url = new File(SparkFiles.getRootDirectory(), localName).toURI.toURL
if (!urlClassLoader.getURLs().contains(url)) {
logInfo("Adding " + url + " to class loader")
urlClassLoader.addURL(url)
}
}

在Utils.fetchFile里还做了一层cache,受参数控制

spark.files.useFetchCache  
而在fetchFile的缓存中,缓存的文件被保存在executor的临时文件夹中,例如
/tmp/spark-e9555893--4a56-a692-54a984c3addb/executor-4b9581ca-fe9f-4e96-9db0-192146158a44/spark-bf41fdbd-a84e-473a-aa60-76480745b50b  

缓存文件的命名规则:

/tmp/spark-e9555893--4a56-a692-54a984c3addb/executor-4b9581ca-fe9f-4e96-9db0-192146158a44/spark-bf41fdbd-a84e-473a-aa60-76480745b50b  

缓存文件的命名规则:

s"${url.hashCode}${timestamp}_cache"  
为了避免同时线程安全问题,可能存在多个任务Fetch相同的文件,FetchFile使用了文件锁,并且是细粒度的文件锁,只增对相同的文件
1. 相同的文件名,这里的文件名也是网络文件名
2. 相同的时间戳
整个完整的流程如下
  1. 检查本地是否有相同的缓存文件
  2. 如果没有,先Fetch文件从Driver中获取,通过URL:(

    spark://192.168.121.101:37684/jars/spark-examples_2.11-2.1.0.jar

    )复制到本地的缓存文件

  3. 复制本地缓存文件到工作目录 /work/app-ID/executorid/
  4. 设置工作目录文件具有可执行权限
最后通过urlClassLoader去loader这个jar文件

2.2 运行task

前面所提到的subBuffer实际上就是Task的序列化对象,通过反序列化可以获取到Driver生成的Task
在Executor.scala里的run方法中
val res = task.run(
taskAttemptId = taskId,
attemptNumber = attemptNumber,
metricsSystem = env.metricsSystem)
最后调用了task.run的方法,在task的run方法,所有继承了Task的类都只需要实现runTask的方法

2.3 反序列化RDD,Dependency

RDD是算子,Dependency是依赖,这是在Executor需要的运算,但是在前面的序列化对象中,并没有看到有RDD,Dep的属性,那么RDD,Dep是怎么传递到Task里进行运算的呢?
在DAG里生成的task就是ShuffleMapTask, ResultTask,下面以ShuffleMapTask为例,在runTask里
val (rdd, dep) = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])](
ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)
_executorDeserializeTime = System.currentTimeMillis() - deserializeStartTime
_executorDeserializeCpuTime = if (threadMXBean.isCurrentThreadCpuTimeSupported) {
threadMXBean.getCurrentThreadCpuTime - deserializeStartCpuTime
} else 0L

也就是基于taskBinary.value来进行反序列化获得,在来看taskBinary成员

taskBinary: Broadcast[Array[Byte]],  
/** Get the broadcasted value. */
def value: T = {
assertValid()
getValue()
}

在前面博客章节中关于Spark Storage管理中提到在集群下使用的是TorrentBroadcast

@transient private lazy val _value: T = readBroadcastBlock()  

在前面的storage 系列(一)里面已经谈到过当本地的broadcastId不存在的时候,会尝试去远端(也就是Driver)获取内容,这里的BroadcastId格式是

broadcast_executorID  

博客中也提到了同一个Executor拥有一个Block,一个大Block也存在多个Piece的小Block, 也就是格式

broadcast_executorID_pieceid  
val blocks = readBlocks().flatMap(_.getChunks())
logInfo("Reading broadcast variable " + id + " took" + Utils.getUsedTimeMs(startTimeMs)) val obj = TorrentBroadcast.unBlockifyObject[T](
blocks, SparkEnv.get.serializer, compressionCodec)
// Store the merged copy in BlockManager so other tasks on this executor don't
// need to re-fetch it.
val storageLevel = StorageLevel.MEMORY_AND_DISK
if (!blockManager.putSingle(broadcastId, obj, storageLevel, tellMaster = false)) {
throw new SparkException(s"Failed to store $broadcastId in BlockManager")
}
在远端获取多个piece块后,在blockManager里会合成一个以broadcast_executorID为key的大block块保存在blockManager里,作为缓存同一个executor下的其他运行的task直接使用blockManager里的块,而不在需要远端在去获取block。
在这里blockManager同时也保存着每个piece的block快,主要考虑到TorrentBroadcast的时候,Executor也可以作为一个传播block块的节点,而不只是Driver的单个节点。
Block里面的内容反序列化后生成RDD和Dependency对象。

2.4 序列化RDD,Dependency

前面讲了executor的反序列化的过程,当然序列化过程是在Driver中做的,回到DAGScheduler.scala的submitMissingTasks函数中
var taskBinary: Broadcast[Array[Byte]] = null
try {
// For ShuffleMapTask, serialize and broadcast (rdd, shuffleDep).
// For ResultTask, serialize and broadcast (rdd, func).
val taskBinaryBytes: Array[Byte] = stage match {
case stage: ShuffleMapStage =>
JavaUtils.bufferToArray(
closureSerializer.serialize((stage.rdd, stage.shuffleDep): AnyRef))
case stage: ResultStage =>
JavaUtils.bufferToArray(closureSerializer.serialize((stage.rdd, stage.func): AnyRef))
} taskBinary = sc.broadcast(taskBinaryBytes)
} catch {
// In the case of a failure during serialization, abort the stage.
case e: NotSerializableException =>
abortStage(stage, "Task not serializable: " + e.toString, Some(e))
runningStages -= stage // Abort execution
return
case NonFatal(e) =>
abortStage(stage, s"Task serialization failed: $e\n${Utils.exceptionString(e)}", Some(e))
runningStages -= stage
return
}
看到序列化的是Stage的rdd和shuffleDependency, 其中是Stage里的rdd就是shuffleDep.rdd也就是ShuffledRDD里prev的RDD

3 总结:

  • TaskDescription 只是包含了任务需要的文件列表,jar文件,配置相关属性,并没有这些具体的文件
  • 具体的文件下载路径是Driver直接在TaskDescription中的serializedTask提供的
  • 具体要运行的Task是通过serializedTask中的subbuffer中反序列化的
  • Task中依赖的RDD,Dependency是从BlockManager从Driver的Block块中获取进行反序列化
  • ShuffleMapTask里依赖的的RDD是ShuffledRDD的前一个RDD,而Dependency就是ShuffleDependency

Spark Core(三)Executor上是如何launch task(转载)的更多相关文章

  1. Spark Core(四)用LogQuery的例子来说明Executor是如何运算RDD的算子(转载)

    1. 究竟是怎么运行的? 很多的博客里大量的讲了什么是RDD, Dependency, Shuffle.......但是究竟那些Executor是怎么运行你提交的代码段的? 下面是一个日志分析的例子, ...

  2. Spark 3.x Spark Core详解 & 性能优化

    Spark Core 1. 概述 Spark 是一种基于内存的快速.通用.可扩展的大数据分析计算引擎 1.1 Hadoop vs Spark 上面流程对应Hadoop的处理流程,下面对应着Spark的 ...

  3. SparkSQL 与 Spark Core的关系

    不多说,直接上干货! SparkSQL 与 Spark Core的关系 Spark SQL构建在Spark Core之上,专门用来处理结构化数据(不仅仅是SQL). Spark SQL在Spark C ...

  4. 大数据:Spark Core(二)Driver上的Task的生成、分配、调度

    1. 什么是Task? 在前面的章节里描写叙述过几个角色,Driver(Client),Master,Worker(Executor),Driver会提交Application到Master进行Wor ...

  5. Spark Core(二)Driver上的Task的生成、分配、调度(转载)

    1. 什么是Task? 在前面的章节里描述过几个角色,Driver(Client),Master,Worker(Executor),Driver会提交Application到Master进行Worke ...

  6. 上万字详解Spark Core(建议收藏)

    先来一个问题,也是面试中常问的: Spark为什么会流行? 原因1:优秀的数据模型和丰富计算抽象 Spark 产生之前,已经有MapReduce这类非常成熟的计算系统存在了,并提供了高层次的API(m ...

  7. spark执行在yarn上executor内存不足异常ERROR YarnScheduler: Lost executor 542 on host-bigdata3: Container marked as failed: container_e40_1550646084627_1007653_01_000546 on host: host-bigdata3. Exit status: 143.

    当spark跑在yarn上时 单个executor执行时,数据量过大时会导致executor的memory不足而使得rdd  最后lost,最终导致任务执行失败 其中会抛出如图异常信息 如图中异常所示 ...

  8. 【Spark Core】TaskScheduler源代码与任务提交原理浅析2

    引言 上一节<TaskScheduler源代码与任务提交原理浅析1>介绍了TaskScheduler的创建过程,在这一节中,我将承接<Stage生成和Stage源代码浅析>中的 ...

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

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

随机推荐

  1. Matlab——filter函数用法

    filter:滤波函数,可用来解差分方程. y = filter(b,a,X) [y,zf] = filter(b,a,X) [y,zf] = filter(b,a,X,zi) y = filter( ...

  2. 【技术分享会】 iOS开发简述

    前言 Objective-C(简称OC)也是面向对象的编程语言,运用的许多面向对象的编程思想和C# . Java .C++等变成语言都是相通的: 本次技术讲座主要讲一些设计模式.设计思想等计算机语言通 ...

  3. C#生成随机验证码例子

    C#生成随机验证码例子: 前端: <tr> <td width=" align="center" valign="top"> ...

  4. 外网电脑配置8G运行内存,运行Android Studio,速度很轻松

    Win 7系统 之前RAM是 4 G,运行Android studio ,再运行浏览器或办公软件时卡的一比.再插入一个 4G内存条,总共8G时,速度嗖的一下就上来了.

  5. 在jmeter测试中模拟不同的带宽环境

    怎么去测试在手机app中和在web的不同的连接速度对服务器的影响呢? 大部分情况下,手机终端用户通过移动网络访问网站.所以在不同的网络连接数据下,我们的网站或程序可以同时处理多少链接?今天,这篇文件就 ...

  6. intellij idea移动至方法块function()末尾的快捷键

    intellij idea移动至方法块末尾的快捷键: 1. move caret to code block end ctrl+] 2. move caret to code block end wi ...

  7. iOS - 网址、链接、网页地址、下载链接等正则表达式匹配(解决url包含中文不能编码的问题)

    DNS规定,域名中的标号都由英文字母和数字组成,每一个标号不超过63个字符,也不区分大小写字母.标号中除连字符(-)外不能使用其他的标点符号.级别最低的域名写在最左边,而级别最高的域名写在最右边.由多 ...

  8. mysql判断一个字符串是否包含某几个字符

    使用locate(substr,str)函数,如果包含,返回>0的数,否则返回0

  9. 【巷子】---Mock---基本使用

    一.mock解决的问题 开发时,后端还没完成数据输出,前端只好写静态模拟数据.数据太长了,将数据写在js文件里,完成后挨个改url.某些逻辑复杂的代码,加入或去除模拟数据时得小心翼翼.想要尽可能还原真 ...

  10. linux如何给程序添加自启动

    我要使我的服务程序在重启系统后也随之自动启动.启动我的服务用到了一个脚本.现在有两个方法: 法1: sudo vi /etc/init.d/rc.local在这里添加启动服务的脚本命令. 这个方法的优 ...