Spark開發-Spark執行模式及原理一

Xlucas發表於2017-10-23

核心
1、介紹Spark的執行模組有哪幾種
2、TaskScheduler和TaskSchedulerBackend介紹
3、Executor介紹

spark的執行模式多種多樣,靈活多變,部署在單機上時,既可以用本地模式執行,也可以用偽分佈模式執行,而當以分散式叢集的方式部署時,也有眾多的執行模式可以供選擇,這取決於叢集的實際情況,底層的資源排程既可以依賴於外部的資源排程框架,也可以使用spark內建的standalone模式,對於外部資源排程框架的支援,目前支援mesos和yarn

1、spark執行模式概述
1、1spark執行模式列表
在實際應用中,spark應用程式的執行模式取決於傳遞給SparkContext的MASTER環境變數的值,個別模式還需要依賴輔助的程式介面來配合使用,目前所有支援的MASTER環境變數由特定的字串或URL所組成。
Lcoal[N]:本地模式,使用N個執行緒
Local cluster[worker,core,Memory]:偽分散式模式,可以配置所需要啟動的虛擬工作節點數量,以及每個工作節點所管理的CPU數量和記憶體大小
Spark://Hostname:port : standalone模式,需要部署spark到相關節點,URL為spark Master主機地址和埠
Mesos://hostname:port : mesos模式,需要部署spark和Mesos到相關節點,URL為Mesos主機地址和埠
YARN Standalone/YARN cluster: YARN模式一:主程式邏輯和任務都執行在YARN叢集中
YARN Client: YARN 模式二:主程式邏輯執行在本地,具體任務執行在YARN叢集中

1、2spark基本工作流程
看到這麼多執行模式,其實內部的工作流程很相似,他們從根本上都是將Spark的應用分為任務排程和任務執行兩個部分,下圖是分散式模式下,Spark的各個排程和執行模組的大致框架圖,對於本地模式來說,其內部程式邏輯結構也是類似的,只是其中部分模組有所簡化,例如叢集管理模組簡化為程式內部的執行緒池。
下圖可以看到,所有的spark應用程式都離不開SparkContext和Executor兩部分,Executor負責執行任務,執行Executor的機器稱為worker節點,SparkContext由使用者程式啟動,通過資源排程模組和Executor通訊,SparkContext和Executor這兩部分的核心程式碼實現在各種執行模式中都是公用的,在它們之上,根據執行部署模式的不同,包裝了不同排程模組以及相關的適配程式碼。
這裡寫圖片描述

具體來說,以SparkContext為程式執行的入口,在SparkContext的初始化過程中,Spark會分別建立DAGScheduler作業排程和TaskScheduler任務排程兩級排程模組。

其中作業排程模組是基於任務階段的高層排程模組,它為每個spark作業計算具有依賴關係的多個排程階段(通常根據shuffle來劃分),然後為每個階段構建出一組具體的任務(通常會考慮資料的本地性等),然後以TaskSets(任務組)的形式提交給任務排程模組來具體執行。而任務排程器則負責具體任務啟動、監控和彙報任務執行情況。

作業排程模組和具體的部署執行模組無關,在各種執行模式下邏輯相同,
不同執行模式的區別主要體現在任務排程模組,不同的部署和 執行模式,根據底層資源排程方式的不同,各自實現了自己特定的任務排程模組,用來將任務實際排程給對應的計算資源。

1、3 相關基本類

TaskScheduler和SchedulerBackend

為了抽象一個公共的介面給DAGScheduler作業排程模組使用,所有的這些執行模式實現的任務排程模組都是基於這兩個介面(Trait)的,TaskScheduler和SchedulerBackend

TaskScheduler程式

private[spark] trait TaskScheduler {
//設定application的ID值
  private val appId = "spark-application-" + System.currentTimeMillis
  def rootPool: Pool
  def schedulingMode: SchedulingMode
  def start(): Unit
  def postStartHook() { }
  def stop(): Unit
//提交待執行的任務集
  def submitTasks(taskSet: TaskSet): Unit
//取消一個排程階段的所有任務
  def cancelTasks(stageId: Int, interruptThread: Boolean)
//設定 DAG Scheduler 用來回撥相關函式
  def setDAGScheduler(dagScheduler: DAGScheduler): Unit
//預設的並行度,作為決定作業並行度的一個引數
  def defaultParallelism(): Int
  def executorHeartbeatReceived(execId: String, taskMetrics: Array[(Long, TaskMetrics)],
    blockManagerId: BlockManagerId): Boolean
  def applicationId(): String = appId
//處理失去丟失的executor
  def executorLost(executorId: String, reason: ExecutorLossReason): Unit
  def applicationAttemptId(): Option[String]
}

TaskScheduler的實現主要用於與DAGScheduler互動,負責任務的具體排程和執行,其核心介面是submitTasks和cancelTasks

private[spark] trait SchedulerBackend {
  private val appId = "spark-application-" + System.currentTimeMillis
  def start(): Unit
  def stop(): Unit
  def reviveOffers(): Unit
  def defaultParallelism(): Int
  def killTask(taskId: Long, executorId: String, interruptThread: Boolean): Unit =
    throw new UnsupportedOperationException
  def isReady(): Boolean = true
  def applicationId(): String = appId
  def applicationAttemptId(): Option[String] = None
  def getDriverLogUrls: Option[Map[String, String]] = None

}

SchedulerBackend的實現是與底層資源排程系統互動(如mesos和yarn),配合TaskScheduler實現具體任務執行所需要的資源分配,核心介面是reviveOffers

這兩者之間的實際互動過程取決於具體欄位排程模式,理論上這兩者的實現的成對匹配工作的,之所以拆分成2部分,是有利於相似的排程模式共享程式碼功能模式,

TaskSchedulerImpl
TaskSchedulerImpl實現了TaskScheduler介面,提供了大多數本地和分散式執行排程模式的任務排程介面

private[spark] class TaskSchedulerImpl(
    val sc: SparkContext,
    val maxTaskFailures: Int,
    isLocal: Boolean = false)
  extends TaskScheduler with Logging

此外它還實現了resourceOffers和statusUpdate這兩個介面供Backend呼叫,用於提供排程資源和更新任務狀態。

def resourceOffers(offers: Seq[WorkerOffer]): Seq[Seq[TaskDescription]]
def statusUpdate(tid: Long, state: TaskState, serializedData: ByteBuffer)

另外,在提交任務和更新狀態等階段,TaskSchedulerImp1都會呼叫Backend的reviveOffers函式,用於發起一次任務資源排程請求。

Executor
實際任務的執行,最終都由Executor類來執行,Executor對每一個任務建立一個TaskRunner類,交給執行緒池執行,
程式碼如下

//啟動執行緒池
private val threadPool = ThreadUtils.newDaemonCachedThreadPool("Executor task launch worker")
//執行任務列表
private val runningTasks = new ConcurrentHashMap[Long, TaskRunner]
def launchTask(
    context: ExecutorBackend,
    taskId: Long,
    attemptNumber: Int,
    taskName: String,
    serializedTask: ByteBuffer): Unit = {
  val tr = new TaskRunner(context, taskId = taskId, attemptNumber = attemptNumber, taskName,
    serializedTask)
  runningTasks.put(taskId, tr)
  threadPool.execute(tr)
}
執行的結果最終通過ExecutorBackend介面返回
private[spark] trait ExecutorBackend {
  def statusUpdate(taskId: Long, state: TaskState, data: ByteBuffer)
}

相關文章