《spark streaming源碼五》JobGenerator 詳解

轉載自https://github.com/lw-lin/CoolplaySpark

本系列內容適用範圍:

* 2017.07.11 update, Spark 2.2 全系列 √ (已發佈:2.2.0)
* 2017.10.02 update, Spark 2.1 全系列 √ (已發佈:2.1.0, 2.1.1, 2.1.2)
* 2016.11.14 update, Spark 2.0 全系列 √ (已發佈:2.0.0, 2.0.1, 2.0.2)

 

引言

前面在 [Spark Streaming 實現思路與模塊概述](Spark Streaming 實現思路與模塊概述) 和 [DStream 生成 RDD 實例詳解](DStream 生成 RDD 實例詳解) 裏我們分析了 DStream 和 DStreamGraph 具有能夠實例化 RDD 和 RDD DAG 的能力,下面我們來看 Spark Streaming 是如何將其動態調度的。

在 Spark Streaming 程序的入口,我們都會定義一個 batchDuration,就是需要每隔多長時間就比照靜態的 DStreamGraph 來動態生成一個 RDD DAG 實例。在 Spark Streaming 裏,總體負責動態作業調度的具體類是 JobScheduler

JobScheduler 有兩個非常重要的成員:JobGenerator 和 ReceiverTrackerJobScheduler 將每個 batch 的 RDD DAG 具體生成工作委託給 JobGenerator,而將源頭輸入數據的記錄工作委託給 ReceiverTracker

 

JobScheduler    的全限定名是:org.apache.spark.streaming.scheduler.JobScheduler
JobGenerator    的全限定名是:org.apache.spark.streaming.scheduler.JobGenerator
ReceiverTracker 的全限定名是:org.apache.spark.streaming.scheduler.ReceiverTracker

本文我們來詳解 JobScheduler

JobGenerator 啓動

在用戶 code 最後調用 ssc.start() 時,將隱含的導致一系列模塊的啓動,其中對我們 JobGenerator 這裏的啓動調用關係如下:

// 來自 StreamingContext.start(), JobScheduler.start(), JobGenerator.start()

ssc.start()                              // 【用戶 code:StreamingContext.start()】
    -> scheduler.start()                 // 【JobScheduler.start()】
                 -> jobGenerator.start() // 【JobGenerator.start()】

具體的看,JobGenerator.start() 的代碼如下:

// 來自 JobGenerator.start()

def start(): Unit = synchronized {
  ...
  eventLoop.start()                      // 【啓動 RPC 處理線程】

  if (ssc.isCheckpointPresent) {
    restart()                            // 【如果不是第一次啓動,就需要從 checkpoint 恢復】
  } else {
    startFirstTime()                     // 【第一次啓動,就 startFirstTime()】
  }
}

可以看到,在啓動了 RPC 處理線程 eventLoop 後,就會根據是否是第一次啓動,也就是是否存在 checkpoint,來具體的決定是 restart() 還是 startFirstTime()

後面我們會分析失效後重啓的 restart() 流程,這裏我們來關注 startFirstTime():

// 來自 JobGenerator.startFirstTime()

private def startFirstTime() {
  val startTime = new Time(timer.getStartTime())
  graph.start(startTime - graph.batchDuration)
  timer.start(startTime.milliseconds)
  logInfo("Started JobGenerator at " + startTime)
}

可以看到,這裏首次啓動時做的工作,先是通過 graph.start() 來告知了 DStreamGraph 第 1 個 batch 的啓動時間,然後就是 timer.start() 啓動了關鍵的定時器。

當定時器 timer 啓動以後,JobGenerator 的 startFirstTime() 就完成了。

RecurringTimer

通過之前幾篇文章的分析我們知道,JobGenerator 維護了一個定時器,週期就是用戶設置的 batchDuration定時爲每個 batch 生成 RDD DAG 的實例

具體的,這個定時器實例就是:

// 來自 JobGenerator

private[streaming]
class JobGenerator(jobScheduler: JobScheduler) extends Logging {
...
  private val timer = new RecurringTimer(clock, ssc.graph.batchDuration.milliseconds,
      longTime => eventLoop.post(GenerateJobs(new Time(longTime))), "JobGenerator")
...
}

 

通過代碼也可以看到,整個 timer 的調度週期就是 batchDuration,每次調度起來就是做一個非常簡單的工作:往 eventLoop 裏發送一個消息 —— 該爲當前 batch (new Time(longTime)) GenerateJobs 了!

GenerateJobs

接下來,eventLoop 收到消息時,會在一個消息處理的線程池裏,執行對應的操作。在這裏,處理 GenerateJobs(time) 消息的對應操作是 generateJobs(time)

private def generateJobs(time: Time) {
  SparkEnv.set(ssc.env)
  Try {
    jobScheduler.receiverTracker.allocateBlocksToBatch(time)                 // 【步驟 (1)】
    graph.generateJobs(time)                                                 // 【步驟 (2)】
  } match {
    case Success(jobs) =>
      val streamIdToInputInfos = jobScheduler.inputInfoTracker.getInfo(time) // 【步驟 (3)】
      jobScheduler.submitJobSet(JobSet(time, jobs, streamIdToInputInfos))    // 【步驟 (4)】
    case Failure(e) =>
      jobScheduler.reportError("Error generating jobs for time " + time, e)
  }
  eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater = false))       // 【步驟 (5)】
}

這段代碼異常精悍,包含了 JobGenerator 主要工作 —— 如下圖所示 —— 的 5 個步驟!

  • (1) 要求 ReceiverTracker 將目前已收到的數據進行一次 allocate,即將上次 batch 切分後的數據切分到到本次新的 batch 裏
    • 這裏 ReceiverTracker 對已收到數據的 meta 信息進行 allocateBlocksToBatch(time),與 ReceiverTracker 自己接收 ReceiverSupervisorImpl 上報塊數據 meta 信息的過程,是相互獨立的,但通過 synchronized 關鍵字來互斥同步
    • 即是說,不管 ReceiverSupervisorImpl 形成塊數據的時間戳 t1ReceiverSupervisorImpl 發送塊數據的時間戳 t2ReceiverTracker 收到塊數據的時間戳 t3 分別是啥,最終塊數據劃入哪個 batch,還是由 ReceiverTracker.allocateBlocksToBatch(time) 方法獲得 synchronized 鎖的那一刻,還有未劃入之前任何一個 batch 的塊數據 meta,將被劃分入最新的 batch
    • 所以,每個塊數據的 meta 信息,將被劃入一個、且只被劃入一個 batch

 

  • (2) 要求 DStreamGraph 複製出一套新的 RDD DAG 的實例,具體過程是:DStreamGraph 將要求圖裏的尾 DStream 節點生成具體的 RDD 實例,並遞歸的調用尾 DStream 的上游 DStream 節點……以此遍歷整個 DStreamGraph,遍歷結束也就正好生成了 RDD DAG 的實例
    • 這個過程的詳解,請參考前面的文章 [DStream 生成 RDD 實例詳解](1.2 DStream 生成 RDD 實例詳解.md)
    • 精確的說,整個 DStreamGraph.generateJobs(time) 遍歷結束的返回值是 Seq[Job]

 

  • (3) 獲取第 1 步 ReceiverTracker 分配到本 batch 的源頭數據的 meta 信息
    • 第 1 步中 ReceiverTracker 只是對 batch 的源頭數據 meta 信息進行了 batch 的分配,本步驟是按照 batch 時間來向 ReceiverTracker 查詢得到劃分到本 batch 的塊數據 meta 信息

 

  • (4) 將第 2 步生成的本 batch 的 RDD DAG,和第 3 步獲取到的 meta 信息,一同提交給 JobScheduler 異步執行
    • 這裏我們提交的是將 (a) time (b) Seq[job] (c) 塊數據的 meta 信息 這三者包裝爲一個 JobSet,然後調用 JobScheduler.submitJobSet(JobSet) 提交給 JobScheduler
    • 這裏的向 JobScheduler 提交過程與 JobScheduler 接下來在 jobExecutor 裏執行過程是異步分離的,因此本步將非常快即可返回

 

  • (5) 只要提交結束(不管是否已開始異步執行),就馬上對整個系統的當前運行狀態做一個 checkpoint
    • 這裏做 checkpoint 也只是異步提交一個 DoCheckpoint 消息請求,不用等 checkpoint 真正寫完成即可返回
    • 這裏也簡單描述一下 checkpoint 包含的內容,包括已經提交了的、但尚未運行結束的 JobSet 等實際運行時信息。

 

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