Task执行内幕与结果处理解密(DT大数据梦工厂)

内容:

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(thistaskId = taskDesc.taskIdattemptNumber = taskDesc.attemptNumber,
      taskDesc.nametaskDesc.serializedTask)
  }

execBackend.statusUpdate(taskIdTaskState.RUNNINGEMPTY_BYTE_BUFFER)

val (taskFilestaskJarstaskBytes) = Task.deserializeWithDependencies(serializedTask)
updateDependencies(taskFilestaskJars)
task = ser.deserialize[Task[Any]](taskBytesThread.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 ((nametimestamp) <- 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(namenew File(SparkFiles.getRootDirectory())conf,
        env.securityManagerhadoopConftimestampuseCache = !isLocal)
      currentFiles(name) = timestamp
    }
    for ((nametimestamp) <- 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(namenew File(SparkFiles.getRootDirectory())conf,
          env.securityManagerhadoopConftimestampuseCache = !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 (valueaccumUpdates) = 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 (rdddep) = ser.deserialize[(RDD[_]ShuffleDependency[___])](
  ByteBuffer.wrap(taskBinary.value)Thread.currentThread.getContextClassLoader)

最终计算会调用RDD的compute(终于登场了!!!)方法

具体计算的时候有具体的RDD,例如MapPartitionsRDD的compute(注意:其中f一般就是我们当前在Stage中写的业务逻辑函数!!!不过会把链条中的很多函数合并成为一个大函数来做):

override def compute(split: Partitioncontext: TaskContext): Iterator[U] =
  f(contextsplit.indexfirstParent[T].iterator(splitcontext))

9、ResultTask根据前面Stage的执行结果进行Shuffle产生Job最后的结果,其中MapOutputTracker会把ShuffleMapTask 执行结果交给ResultTask,

里面会调用反序列化之后rdd中的func:

val (rddfunc) = ser.deserialize[(RDD[T](TaskContextIterator[T]) => U)](
  ByteBuffer.wrap(taskBinary.value)Thread.currentThread.getContextClassLoader)
_executorDeserializeTime = System.currentTimeMillis() - deserializeStartTime

metrics Some(context.taskMetrics)
func(contextrdd.iterator(partitioncontext))

10、在Spark1.6中,AkkaFrameSize是128M,所以可以广播非常大的任务,而任务的执行结果可以最大达到1GB:

// directSend = sending directly back to the driver
val serializedResult: ByteBuffer = {
  if (maxResultSize && 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(
      blockIdserializedDirectResultStorageLevel.MEMORY_AND_DISK_SER)
    logInfo(
      s"Finished $taskName (TID $taskId). $resultSize bytes result sent via BlockManager)")
    ser.serialize(new IndirectTaskResult[Any](blockIdresultSize))
  } 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(taskIdTaskState.FINISHEDserializedResult)

补充说明:

1、在执行具体Task业务逻辑前会进行三次反序列化:

1)TaskDesciption的反序列化;

2)反序列化Task的依赖;

3)Task的反序列化;

4)RDD的反序列化;

spacer.gif

王家林老师名片:

中国Spark第一人

新浪微博:http://weibo.com/ilovepains

微信公众号:DT_Spark

博客:http://blog.sina.com.cn/ilovepains

手机:18610086859

QQ:1740415547

邮箱:[email protected]


本文出自 “一枝花傲寒” 博客,谢绝转载!

你可能感兴趣的:(task,执行内幕,结果处理)