Spark之Task的定義

一個供Executor執行的可執行的邏輯單元,Spark目前提供了兩類Task,分別爲ShuffleMapTask和ResultTask。Job會由一個或者多個Stage,一個Spark Job基於Stage構建成邏輯執行計劃和物理執行計劃。如: Job A={S1,S2,S3} 由三個Stage構成,那麼S1、S2會由ShuffleMapTasks構成,S3作爲Job的最後一個Stage由多個ResultTasks構成。Task在Driver側構建,在Executor側執行,執行完一個ResultTask會把Task的輸出發送給Driver側的應用程序,執行完一個ShuffleMapTask後基於Task的Partitioner把Task的輸出劃分多份進行存儲。

如上圖:一個Stage內只會存在同一種TaskTask數量與StagePartition數量保持一致(運行的Task數量可能會大於Partition數量)

構造函數:

private[spark] abstract class Task[T](

//Task對應的Stage唯一ID號

val stageId: Int,

//stageAttemptId表示Stage被執行的次數,第一次爲1
val stageAttemptId: Int,

//Task對應的partitionid,一個Task對一個唯一的partitionid
val partitionId: Int,
@transient var localProperties: Properties = new Properties,
// The default value is only used in tests.
serializedTaskMetrics: Array[Byte] =
      SparkEnv.get.closureSerializer.newInstance().serialize(TaskMetrics.registered).array(),
val jobId: Option[Int] = None,
val appId: Option[String] = None,
val appAttemptId: Option[String] = None) extends Serializable {}

屬性:

//在MapOutputTracker中定義的,記錄Map側失敗的次數,由Driver側維護。Executor每次執行Task的時候會檢查loca epoch<epoch,會更新epoch,並清除Executor的緩存的mapStatuses。

//在Local Mode下epoch沒有意義

var epoch: Long = -1

// TaskContext在run方法中初始化,即在Executor側初始化
@transient var context: TaskContextImpl = _

TaskContextImpl記錄了一個Task的上下文信息,包括Task的重要屬性和執行結果。

上下文屬性:

屬性

說明

stageid

Stage的唯一ID號

stageAttemptNumber

Stage被執行次數,每失敗一次加一,由Driver側維護

partitionid

當前Task所對應的Partitionid,一對一的關係

attemptNumber

Task被執行的次數,每失敗一次加一,由Driver側維護

執行結果:

方法

說明

addTaskCompletionListener

添加任務完成的事件,任務失敗後也會添加完成事件

addTaskFailureListener

添加任務失敗事件

方法:

//由Executor執行,ShuffleMapTask、ResultTask分別實現了runTask,詳情請見Task執行小節

def runTask(context: TaskContext): T 

 

綜上述,Spark中的Task主要信息包括 Stage、Partitioner、TaskContext等這些Spark Core的概念,不包括RDD和用戶的編程邏輯代碼。Driver通過Broadcast把RDD廣播給所有的Executor,通過Endpoint把Task點對點的發送到指定的Executor。

注意:RDD與Stage分開存在一個明顯的性能問題,Executor每次執行Task的時候都需要返序列化一次,下圖爲ShuffleMapTask中反序列化的代碼。

// RDD本身數據是通過BroadCast廣播出去給所有Executor,這裏通過BroadCast獲取RDD並反序列化
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

我們都知道反序列化是一個很消耗CPU的操作,對於大數據計算框架CPU資源顯得更加珍貴。在DAGScheduler.submitMissingTasks方法中Spark的開發人員有考慮過把RDD放到Stage裏面以避免多次的反序列化。通過BroadCast廣播給所有的Executor,可以確保每個Task都可獨佔一個RDD序列化後的副本,在Task之間可以提供很強的隔離性。他們擔心用戶在RDD的Function閉包中通過對象引用修改對象的狀態,例如:Hadoop中JobConf/Configuration對象不是線程安全的。

// TODO: Maybe we can keep the taskBinary in Stage to avoid serializing it multiple times.
// Broadcasted binary for the task, used to dispatch tasks to executors. Note that we broadcast
// the serialized copy of the RDD and for each task we will deserialize it, which means each
// task gets a different copy of the RDD. This provides stronger isolation between tasks that
// might modify state of objects referenced in their closures. This is necessary in Hadoop
// where the JobConf/Configuration object is not thread-safe.
var taskBinary: Broadcast[Array[Byte]] = null

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章