SparkCore — Task执行源码分析之TaskRunner.run()

Task原理与源码分析

  在Executor注册完成之后,接收到Driver发送的LaunchTask消息之后,会调用executor执行句柄的launchTask()方法,里面封装了TaskRunner线程,然后将其放入线程池中运行,下面看一下TaskRunner的run()方法。
  由于run方法代码比较长,我把它分为三个部分来说,一个是准备工作,一个是task的执行,一个是task执行结束后的工作。先看准备工作:

override def run(): Unit = {
      // 创建task运行时需要的一些组件
      // 创建task的内存管理器
      val taskMemoryManager = new TaskMemoryManager(env.memoryManager, taskId)
      // 反序列化task开始时间
      val deserializeStartTime = System.currentTimeMillis()
      // 创建类加载器
      Thread.currentThread.setContextClassLoader(replClassLoader)
      // 序列化对象
      val ser = env.closureSerializer.newInstance()
      logInfo(s"Running $taskName (TID $taskId)")
      // 更新executor的状态, 向(Driver)SparkDeploySchedulerBackend发送StatusUpdate,也即Task正在运行的消息
      execBackend.statusUpdate(taskId, TaskState.RUNNING, EMPTY_BYTE_BUFFER)
      var taskStart: Long = 0
      // 记录GC开始时间
      startGCTime = computeTotalGcTime()

      try {
        // 对序列化的task数据,进行反序列化
        val (taskFiles, taskJars, taskBytes) = Task.deserializeWithDependencies(serializedTask)
        // 通过网络通信,将需要的资源、文件、Jar拷贝过来。
        updateDependencies(taskFiles, taskJars)
        // 通过正式的反序列化操作,将整个task的数据集反序列化回来
        // 类加载器,Java的ClassLoader的作用,比如可以使用反射的方式来动态加载一个类,然后创建这个类的对象
        // 还有可以对指定上下文的相关资源,进行加载和读取
        task = ser.deserialize[Task[Any]](taskBytes, Thread.currentThread.getContextClassLoader)
        task.setTaskMemoryManager(taskMemoryManager)
        // 如果task被kill,抛异常
        if (killed) {
          throw new TaskKilledException
        }
   }

  上面是Task运行前的一些准备工作,主要是开启一些计时器,其中比较重要的是updateDependencies()这个方法,它会从网络拉取Task运行所需的文件和Jar包,支持拉取Hadoop兼容的文件系统等等。
  接着就是执行task的部分:

	// 统计Task的开始时间
    taskStart = System.currentTimeMillis()
     var threwException = true
     // 这里的value对于ShuffleMapTask来说,其实就是MapStatus,
     // 封装了ShuffleMapTask计算的数据,输出的位置
     // 后面如果还是一个ShuffleMapTask,就会去联系MapOutputTracker。来获取上一个ShuffleMapTask的输出位置
     // 然后通过网络拉取数据,ResultTask也是一样的。
     val (value, accumUpdates) = try {
       // 执行task,用的task的run方法
       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)
         }
       }
     }
     // 统计结束时间
     val taskFinish = System.currentTimeMillis()

  上面就是task的运行部分,主要是调用了task.run()方法,这里的它有两个返回值,一个是value,一个是accumUpdates(这个没有研究,有大神知道的话,希望能够告知)。其中value对于ShuffleMapTask而言就是Task执行结束后Map的状态,MapStatus,里面包含了执行结果数据的位置等信息。
  Task运行结束之后,下面看一下对task运行结果的一些操作:

 		// 获取序列化的对象
        val resultSer = env.serializer.newInstance()
        val beforeSerialization = System.currentTimeMillis()
        // 对task输出结果进行序列化
        val valueBytes = resultSer.serialize(value)
        val afterSerialization = System.currentTimeMillis()

        // 统计出Task相关的运行时间,这些会在Spark UI上显示,大家争着在企业中运行我们的长时间
        for (m <- task.metrics) {
          // Deserialization happens in two parts: first, we deserialize a Task object, which
          // includes the Partition. Second, Task.run() deserializes the RDD and function to be run.
          // 运行了多久
          m.setExecutorDeserializeTime(
            (taskStart - deserializeStartTime) + task.executorDeserializeTime)
          // We need to subtract Task.run()'s deserialization time to avoid double-counting
          // 反序列化时间
          m.setExecutorRunTime((taskFinish - taskStart) - task.executorDeserializeTime)
          // JVM GC的时间
          m.setJvmGCTime(computeTotalGcTime() - startGCTime)
          // values的序列化耗费多久时间
          m.setResultSerializationTime(afterSerialization - beforeSerialization)
          m.updateAccumulators()
        }

        // 将Task序列化的运行结果封装为DirectTaskResult
        val directResult = new DirectTaskResult(valueBytes, accumUpdates, task.metrics.orNull)
        // 在进行序列化
        val serializedDirectResult = ser.serialize(directResult)
        // 序列化后的大小
        val resultSize = serializedDirectResult.limit

        // 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
          }
        }

        // 这个就非常重要,就是调用了executor所在的CoarseGrainedExecutorBackend的statusUpdate方法
        execBackend.statusUpdate(taskId, TaskState.FINISHED, serializedResult)

  从源码来看,先对Task运行结果value进行一些序列化操作, 然后统计Task运行时候的一些时间信息,比如GC的时间,Task运行时间等;在将结果进行一些封装之后,在序列化为serializedDirectResult,这里面用到了BlockManager组件(shuffle底层的内存管理组件,后面再单独分析它),接着就调用executor所在的CoarseGrainedExecutorBackend的statusUpdate()方法发送StatusUpdate消息给Driver的SparkDeploySchedulerBackend。

  上面是TaskRunner.run()线程的执行逻辑,下一篇博客分析TaskRunner.run()方法中调用的两个比较重要方法的task.run()和statusUpdate()。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值