Desencadenar la ejecución especulativa

La ejecución especulativa de Spark es una técnica de optimización.

En Spark, la ejecución especulativa, es decir, la ejecución especulativa, se puede utilizar para identificar y reiniciar algunas tareas de ejecución lenta en el ejecutor de otros nodos y procesar los mismos datos en paralelo. La tarea completada se elimina, lo que acelera la tarea velocidad de procesamiento. Es adecuado para escenarios en los que algunas tareas de Spark se suspenden o se ejecutan lentamente, lo que ralentiza la velocidad de ejecución de toda la tarea.

Nota:

    1. No todas las tareas de Spark que se ejecutan lentamente pueden resolverse mediante una ejecución especulativa.

    2. Tenga cuidado al utilizar la ejecución especulativa. Se requieren escenarios apropiados y parámetros apropiados Los parámetros irrazonables pueden hacer que un gran número de tareas de ejecución especulativa ocupen recursos.

    3. Si Spark Streaming escribe en Kafka lentamente, si la ejecución especulativa está habilitada, puede producirse la duplicación de datos.

     4. La tarea que se especula no se volverá a especular.

Spark parámetros de ejecución especulativa

spark.speculation: el valor predeterminado es falso. Ya sea para habilitar la ejecución especulativa.

spark.speculation.interval: el valor predeterminado es 100ms. Con qué frecuencia comprobar las tareas a ejecutar de forma especulativa.

spark.speculation.multiplier: el valor predeterminado es 1.5. En una etapa, las tareas cuyo tiempo de ejecución es 1,5 veces más lento que el tiempo medio de ejecución de las tareas completadas con éxito pueden ejecutarse especulativamente.

spark.speculation.quantile: el valor predeterminado es 0,75. Cuantil inferido. Es decir, en una etapa, al menos el 75% de las tareas deben completarse antes de que comience la especulación.

Spark análisis de código fuente de ejecución especulativa

Análisis de código fuente

  /**
    * TaskScheduleImpl在启动时,会判断是否启动Task的推测执行。
    */
  override def start() {
    backend.start()
    if (!isLocal && conf.getBoolean("spark.speculation", false)) {
      logInfo("Starting speculative execution thread")
      // scheduleWithFixedDelay 位于`java.util.concurrent.ScheduledExecutorService#scheduleWithFixedDelay`
      // scheduleWithFixedDelay 指的是系统启动等待`第一个SPECULATION_INTERVAL_MS 时间后`,开始执行定时任务,每隔`第二个SPECULATION_INTERVAL_MS 时间`执行一次。
      // SPECULATION_INTERVAL_MS 可通过`spark.speculation.interval`参数设置
      speculationScheduler.scheduleWithFixedDelay(new Runnable {
        override def run(): Unit = Utils.tryOrStopSparkContext(sc) {
          // 检查需要推测执行的Task
          checkSpeculatableTasks()
        }
      }, SPECULATION_INTERVAL_MS, SPECULATION_INTERVAL_MS, TimeUnit.MILLISECONDS)
    }
  }

Si la ejecución especulativa de Spark está habilitada (es decir, el parámetro spark.speculation = true está establecido) y no se está ejecutando en modo Local, TaskScheduleImpl iniciará spark.speculation.interval (es decir, la primera SPECULATION_INTERVAL_MS) vez, y cada spark.speculation.interval (es decir, el segundo SPECULATION_INTERVAL_MS mencionado anteriormente) tiempo para iniciar un hilo para verificar las tareas que deben ejecutarse especulativamente.

Haga clic en el método checkSpeculatableTasks () para saltar a org.apache.spark.scheduler.checkSpeculatableTasks, el siguiente código:
 

 def checkSpeculatableTasks() {
    var shouldRevive = false
    synchronized {
      // MIN_TIME_TO_SPECULATION 在原始副本运行至少这段时间后,才会启动任务的重复副本。 
      shouldRevive = rootPool.checkSpeculatableTasks(MIN_TIME_TO_SPECULATION)
    }
    if (shouldRevive) {
      // 如果有需要推测执行的Task,则SchedulerBackend向ApplicationMaster发送reviveOffers消息,获取集群中可用的executor列表,发起task
      backend.reviveOffers()
    }
  }

Como puede ver, la llamada interna de este método es el rootPool.checkSpeculatableTasks(MIN_TIME_TO_SPECULATION)siguiente código:

  override def checkSpeculatableTasks(minTimeToSpeculation: Int): Boolean = {
    var shouldRevive = false
    //schedulableQueue是ConcurrentLinkedQueue[Schedulable]类型,而Schedulable Trait有两种类型的调度实体:Pool、TaskSetManager
    for (schedulable <- schedulableQueue.asScala) {
      shouldRevive |= schedulable.checkSpeculatableTasks(minTimeToSpeculation)
    }
    shouldRevive
  }

Como puede ver, finalmente se llama al método schedule.checkSpeculatableTasks (minTimeToSpeculation).

El programable es un objeto en el programableQueue, el programableQueue es del tipo ConcurrentLinkedQueue [Programable] y el Rasgo programado tiene dos tipos de entidades de programación: Pool y TaskSetManager.

Al ver el método org.apache.spark.scheduler.TaskSetManager # checkSpeculatableTasks, puede ver la lógica de la detección real de tareas especulativas. como sigue:
 

//真正检测推测执行Task的逻辑
  override def checkSpeculatableTasks(minTimeToSpeculation: Int): Boolean = {
    // Can't speculate if we only have one task, and no need to speculate if the task set is a
    // zombie or is from a barrier stage.
    if (isZombie || isBarrier || numTasks == 1) {
      return false
    }
    var foundTasks = false
    // minFinishedForSpeculation=SPECULATION_QUANTILE * numTasks
    // SPECULATION_QUANTILE即spark.speculation.quantile
    // numTasks即某个Stage中Taskset的任务总数。
    val minFinishedForSpeculation = (SPECULATION_QUANTILE * numTasks).floor.toInt
    logDebug("Checking for speculative tasks: minFinished = " + minFinishedForSpeculation)

    // 1)已经成功的Task数必须要大于等于`spark.speculation.quantile * numTasks`,才开始处理这个TaskSet
    if (tasksSuccessful >= minFinishedForSpeculation && tasksSuccessful > 0) {
      val time = clock.getTimeMillis()
      // medianDuration: 已经成功的Task的运行时间的中位数
      // threshold=max(SPECULATION_MULTIPLIER * medianDuration, minTimeToSpeculation)
      // SPECULATION_MULTIPLIER:即spark.speculation.multiplier
      val medianDuration = successfulTaskDurations.median
      val threshold = max(SPECULATION_MULTIPLIER * medianDuration, minTimeToSpeculation)
      // TODO: Threshold should also look at standard deviation of task durations and have a lower
      // bound based on that.
      logDebug("Task length threshold for speculation: " + threshold)
      // 2)遍历TaskSet中的每一个Task
      for (tid <- runningTasksSet) {
        val info = taskInfos(tid)
        val index = info.index
        // 3)如果还未运行成功 且 正在执行 且 运行时间已经超过threshold 且 当前不是推测运行的Task
        // 就将该Task取出放到需要推测执行的列表中
        if (!successful(index) && copiesRunning(index) == 1 && info.timeRunning(time) > threshold &&
          !speculatableTasks.contains(index)) {
          logInfo(
            "Marking task %d in stage %s (on %s) as speculatable because it ran more than %.0f ms"
              .format(index, taskSet.id, info.host, threshold))
          speculatableTasks += index
          // 4)最终由DAGScheduler将Task提交到待执行的队列中,后台线程将对提交的任务进行处理
          sched.dagScheduler.speculativeTaskSubmitted(tasks(index))
          foundTasks = true
        }
      }
    }
    foundTasks
  }

El flujo general de la tarea de detección y especulación.

Ejemplo de ejecución especulativa de Spark

Ejemplo de código

package com.bigdata.spark

import org.apache.spark.TaskContext
import org.apache.spark.sql.SparkSession
import org.slf4j.LoggerFactory

/**
  * Author: Wang Pei
  * License: Copyright(c) Pei.Wang
  * Summary: 
  *   Spark推测执行
  */
object SparkSpeculative {
  def main(args: Array[String]): Unit = {

    @transient lazy val logger = LoggerFactory.getLogger(this.getClass)

    val spark=SparkSession.builder()
      //启用Spark推测执行
      .config("spark.speculation",true)
      .config("spark.speculation.interval",1000)
      .config("spark.speculation.multiplier",1.5)
      .config("spark.speculation.quantile",0.10)
      .getOrCreate()


    logger.info("开始处理.........................................")

     //设置5个并行度,一个Stage中,5个Task同时运行
     //为保证5个Task同时运行,Spark Submit提交任务时给5个核
     //这样,方便观察第4个Task被推测执行
    spark.sparkContext.parallelize(0 to 50,5)
      .foreach(item=>{
        if(item ==38){Thread.sleep(200000)}
        val taskContext = TaskContext.get()
        val stageId = taskContext.stageId()
        val taskAttemptId = taskContext.taskAttemptId()
        logger.info(s"当前Stage:${stageId},Task:${taskAttemptId},打印的数字..............${item}..................")
      })

    logger.info("处理完成.........................................")
  }
}

Envío de tareas

/data/apps/spark-2.4.0-bin-2.7.3.2.6.5.3-10/bin/spark-submit \
    --master yarn \
    --deploy-mode cluster \
    --driver-memory 1g \
    --executor-memory 1g \
    --executor-cores  1 \
    --num-executors  5 \
    --queue offline \
    --name SparkSpeculative \
    --class com.bigdata.spark.SparkSpeculative \
    bigdata_spark.jar

Vista de registro de hilo

Puede ver los siguientes registros en Yarn:

19/03/31 04:21:39 INFO scheduler.TaskSetManager: Finished task 2.0 in stage 0.0 (TID 2) in 318 ms on x.x.x.x (executor 4) (1/5)
19/03/31 04:21:39 INFO scheduler.TaskSetManager: Finished task 4.0 in stage 0.0 (TID 4) in 321 ms on x.x.x.x (executor 2) (2/5)
19/03/31 04:21:39 INFO scheduler.TaskSetManager: Finished task 0.0 in stage 0.0 (TID 0) in 338 ms on x.x.x.x (executor 1) (3/5)
19/03/31 04:21:39 INFO scheduler.TaskSetManager: Finished task 1.0 in stage 0.0 (TID 1) in 327 ms on x.x.x.x (executor 3) (4/5)
#task 3被标记为推测执行
19/03/31 04:21:40 INFO scheduler.TaskSetManager: Marking task 3 in stage 0.0 (on x.x.x.x) as speculatable because it ran more than 486 ms
#启动task 3的推测执行task(taskID=5)
19/03/31 04:21:40 INFO scheduler.TaskSetManager: Starting task 3.1 in stage 0.0 (TID 5, x.x.x.x, executor 3, partition 3, PROCESS_LOCAL, 7855 bytes)
#kill掉task 3的推测执行task(taskID=5),由于原来的task已经成功
19/03/31 04:24:59 INFO scheduler.TaskSetManager: Killing attempt 1 for task 3.1 in stage 0.0 (TID 5) on x.x.x.x as the attempt 0 succeeded on x.x.x.x
19/03/31 04:24:59 INFO scheduler.TaskSetManager: Finished task 3.0 in stage 0.0 (TID 3) in 200311 ms on x.x.x.x (executor 5) (5/5)

Vista de Spark WebUI

Puede ver los siguientes resultados en Spark WebUI:

Supongo que te gusta

Origin blog.csdn.net/qq_32445015/article/details/115308734
Recomendado
Clasificación