内容:
1、Task执行原理流程图;
2、Task执行内幕源码解密;
3、Task执行结果在Driver上处理解密;
==========Task执行原理流程图 ============
1、当Driver中的CoarseGaraindSchedulerBackend给CoarseGrainedExecutorBackend发送launchTask之后,CoarseGrainedExecutorBackend 在收到launchTask消息之后,首先会反序列化TaskDescription, ,Executor会通过TaskRunner在ThreadPool中来运行具体的Task;
2、运行Thread的run方法,导致Task的runTask被调用来执行具体的业务逻辑处理,在TaskRunner的run方法中首先会通过调用statusUpdate给Driver发信息汇报自己的状态说明自己是RUNNING状态 ,TaskRunner内部会做一些准备工作,例如反序列化Task的依赖,然后通过网络来获取需要的文件、jar等,然后反序列化Task本身;
case LaunchTask(data) =>
if (executor == null) {
logError("Received LaunchTask command but executor was null")
System.exit(1)
} 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)
}
execBackend.statusUpdate(taskId, TaskState.RUNNING, EMPTY_BYTE_BUFFER)
val (taskFiles, taskJars, taskBytes) = Task.deserializeWithDependencies(serializedTask)
updateDependencies(taskFiles, taskJars)
task = ser.deserialize[Task[Any]](taskBytes, Thread.currentThread.getContextClassLoader)
下面的方法为啥要synchronized?因为Executor有多个线程来下载,而每个线程其实需要的jar以及依赖是一模一样的,所以必须加锁!!!
/**
* Download any missing dependencies if we receive a new set of files and JARs from the
* SparkContext. Also adds any new JARs we fetched to the class loader.
*/
private def updateDependencies(newFiles: HashMap[String, Long], newJars: HashMap[String, Long]) {
lazy val hadoopConf = SparkHadoopUtil.get.newConfiguration(conf)
synchronized {
// Fetch missing dependencies
for ((name, timestamp) <- newFiles if currentFiles.getOrElse(name, -1L) < 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)
currentFiles(name) = timestamp
}
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)
}
}
}
}
}
3、调用反序列化后的taskRun方法来执行任务,并获得执行结果:
val (value, accumUpdates) = try {
val res = task.run(
taskAttemptId = taskId,
attemptNumber = attemptNumber,
metricsSystem = env.metricsSystem)
threwException = false
res
} finally {
val freedMemory = taskMemoryManager.cleanUpAllAllocatedMemory()
if (freedMemory > 0) {
val errMsg = s"Managed memory leak detected; size = $freedMemory bytes, TID = $taskId"
if (conf.getBoolean("spark.unsafe.exceptionOnMemoryLeak", false) && !threwException) {
throw new SparkException(errMsg)
} else {
logError(errMsg)
}
}
}
其中Task的run方法调用的时候,会导致我们的Task的抽象方法runTask的调用,在Task的内部会调用RDD的iterator()方法,该方法就是我们针对当前Task所对应的partition进行计算的关键之所在,在具体处理内部会迭代partition的元素并交给自定的function进行处理;
7、把执行结果序列化:
val resultSer = env.serializer.newInstance()
8、ShuffleMapTask在计算具体的partition之后,实际上会 ShuffleManager获得的ShuffleWriter把当前Task计算的根据具体的ShuffleManager的实现来写写入到具体的文件,操作完成后会把MapStatus发送给DAGScheduler中的MapOutputTracker,
操作上首先要对RDD及其依赖关系要进行反序列化
val (rdd, dep) = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])](
ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)
最终计算会调用RDD的compute(终于登场了!!!)方法
具体计算的时候有具体的RDD,例如MapPartitionsRDD的compute(注意:其中f一般就是我们当前在Stage中写的业务逻辑函数!!!不过会把链条中的很多函数合并成为一个大函数来做):
override def compute(split: Partition, context: TaskContext): Iterator[U] =
f(context, split.index, firstParent[T].iterator(split, context))
9、ResultTask根据前面Stage的执行结果进行Shuffle产生Job最后的结果,其中MapOutputTracker会把ShuffleMapTask 执行结果交给ResultTask,
里面会调用反序列化之后rdd中的func:
val (rdd, func) = ser.deserialize[(RDD[T], (TaskContext, Iterator[T]) => U)](
ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)
_executorDeserializeTime = System.currentTimeMillis() - deserializeStartTime
metrics = Some(context.taskMetrics)
func(context, rdd.iterator(partition, context))
10、在Spark1.6中,AkkaFrameSize是128M,所以可以广播非常大的任务,而任务的执行结果可以最大达到1GB:
// directSend = sending directly back to the driver
val serializedResult: ByteBuffer = {
if (maxResultSize > 0 && resultSize > maxResultSize) {
logWarning(s"Finished $taskName (TID $taskId). Result is larger than maxResultSize " +
s"(${Utils.bytesToString(resultSize)} > ${Utils.bytesToString(maxResultSize)}), " +
s"dropping it.")
ser.serialize(new IndirectTaskResult[Any](TaskResultBlockId(taskId), resultSize))
} else if (resultSize >= akkaFrameSize - AkkaUtils.reservedSizeBytes) {
val blockId = TaskResultBlockId(taskId)
env.blockManager.putBytes(
blockId, serializedDirectResult, StorageLevel.MEMORY_AND_DISK_SER)
logInfo(
s"Finished $taskName (TID $taskId). $resultSize bytes result sent via BlockManager)")
ser.serialize(new IndirectTaskResult[Any](blockId, resultSize))
} else {
logInfo(s"Finished $taskName (TID $taskId). $resultSize bytes result sent to driver")
serializedDirectResult
}
}
// Limit of bytes for total size of results (default is 1GB)
private val maxResultSize = Utils.getMaxResultSize(conf)
根据发小判断不同的传回结果,传回给Driver的方式:大于1G,直接drop,大于1G-200K,用BlockManager来处理返回,小于128M-200K,直接返回!!!
11、CoarseGrainedExecutorBackend给DrvierEndpoint发送statusUpdate来传出执行结果,DriverEndpoint会把执行结果传递给TaskSchedulerImpl处理,然后交给TaskResultGetter内部通过线程去分别处理Task执行成功和失败时候的不同情况,然后告诉DAGScheduler任务处理结果的状况;
execBackend.statusUpdate(taskId, TaskState.FINISHED, serializedResult)
补充说明:
1、在执行具体Task业务逻辑前会进行三次反序列化:
1)TaskDesciption的反序列化;
2)反序列化Task的依赖;
3)Task的反序列化;
4)RDD的反序列化;
王家林老师名片:
中国Spark第一人
新浪微博:http://weibo.com/ilovepains
微信公众号:DT_Spark
博客:http://blog.sina.com.cn/ilovepains
手机:18610086859
QQ:1740415547
本文出自 “一枝花傲寒” 博客,谢绝转载!