《spark streaming源碼六》Receiver 分發詳解

轉載自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)

引言

我們前面在 [DStream, DStreamGraph 詳解](DStream, DStreamGraph 詳解) 講到,整個 DStreamGraph 是由 output stream 通過 dependency 引用關係,索引到上游 DStream 節點。而遞歸的追溯到最上游的 InputDStream 節點時,就沒有對其它 DStream 節點的依賴了,因爲 InputDStream 節點本身就代表了最原始的數據集。

 我們對 模塊 3:數據產生與導入 細節的解釋,是僅針對 ReceiverInputDStream 及其子類的;其它 InputDStream 子類的講解,我們在另外的文章中進行。即,本模塊的討論範圍是:

- ReceiverInputDStream
  - 子類 SocketInputDStream
  - 子類 TwitterInputDStream
  - 子類 RawInputDStream
  - 子類 FlumePollingInputDStream
  - 子類 MQTTInputDStream
  - 子類 FlumeInputDStream
  - 子類 PluggableInputDStream
  - 子類 KafkaInputDStream

 

ReceiverTracker 分發 Receiver 過程

我們已經知道,ReceiverTracker 自身運行在 driver 端,是一個管理分佈在各個 executor 上的 Receiver 的總指揮者。

在 ssc.start() 時,將隱含地調用 ReceiverTracker.start();而 ReceiverTracker.start() 最重要的任務就是調用自己的 launchReceivers() 方法將 Receiver 分發到多個 executor 上去。然後在每個 executor 上,由 ReceiverSupervisor 來分別啓動一個 Receiver 接收數據。這個過程用下圖表示:

我們將以 1.4.0 和 1.5.0 這兩個版本爲代表,仔細分析一下 launchReceivers() 的實現。

1.4.0 代表了 1.5.0 以前的版本,如 1.2.x, 1.3.x, 1.4.x

1.5.0 代表了 1.5.0 以來的版本,如 1.5.x, 1.6.x

 

Spark 1.4.0 的 launchReceivers() 實現

Spark 1.4.0 的 launchReceivers() 的過程如下:

  • (1.a) 構造 Receiver RDD。具體的,是先遍歷所有的 ReceiverInputStream,獲得將要啓動的所有 x 個 Receiver 的實例。然後,把這些實例當做 x 份數據,在 driver 端構造一個 RDD 實例,這個 RDD 分爲 x 個 partition,每個 partition 包含一個 Receiver 數據(即 Receiver 實例)。

  • (1.b) 定義計算 func。我們將在多個 executor 上共啓動 x 個 Task,每個 Task 負責一個 partition 的數據,即一個 Receiver 實例。我們要對這個 Receiver 實例做的計算定義爲 func 函數,具體的,func 是:

    • 以這個 Receiver 實例爲參數,構造新的 ReceiverSupervisor 實例 supervisorsupervisor = new ReceiverSupervisorImpl(receiver, ...)
    • supervisor.start();這一步將啓動新線程啓動 Receiver 實例,然後很快返回
    • supervisor.awaitTermination();將一直 block 住當前 Task 的線程
  • (1.c) 分發 RDD(Receiver) 和 func 到具體的 executor。上面 (a)(b) 兩步只是在 driver 端定義了 RDD[Receiver] 和 這個 RDD 之上將執行的 func,但並沒有具體的去做。這一步是將兩者的定義分發到 executor 上去,馬上就可以實際執行了。

  • (2) 在各個 executor 端,執行(1.b) 中定義的 func。即啓動 Receiver 實例,並一直 block 住當前線程。

這樣,通過 1 個 RDD 實例包含 x 個 Receiver,對應啓動 1 個 Job 包含 x 個 Task,就可以完成 Receiver 的分發和部署了。上述 (1.a)(1.b)(1.c)(2) 的過程示意如下圖:

這裏 Spark Streaming 下層的 Spark Core 對 Receiver 分發是毫無感知的,它只是執行了“應用層面” -- 對 Spark Core 來講,Spark Streaming 就是“應用層面”-- 的一個普通 Job;但 Spark Streaming 只通過這個普通 Job 即可完“特殊功能”的 Receiver 分發,可謂巧妙巧妙。

Spark 1.5.0 的 launchReceivers() 實現

其實上面這個實現,這個長時運行的分發 Job 還存在一些問題:

  • 如果某個 Task 失敗超過 spark.task.maxFailures(默認=4) 次的話,整個 Job 就會失敗。這個在長時運行的 Spark Streaming 程序裏,Executor 多失效幾次就有可能導致 Task 失敗達到上限次數了。
  • 如果某個 Task 失效一下,Spark Core 的 TaskScheduler 會將其重新部署到另一個 executor 上去重跑。但這裏的問題在於,負責重跑的 executor 可能是在下發重跑的那一刻是正在執行 Task 數較少的,但不一定能夠將 Receiver 分佈的最均衡的。
  • 有個用戶 code 可能會想自定義一個 Receiver 的分佈策略,比如所有的 Receiver 都部署到同一個節點上去。

從 1.5.0 開始,Spark Streaming 添加了增強的 Receiver 分發策略。對比之前的版本,主要的變更在於:

  1. 添加可插拔的 ReceiverSchedulingPolicy
  2. 把 1 個 Job(包含 x 個 Task),改爲 x 個 Job(每個 Job 只包含 1 個 Task
  3. 添加對 Receiver 的監控重啓機制

我們一個一個看一看。

(1) 可插拔的 ReceiverSchedulingPolicy

ReceiverSchedulingPolicy 的主要目的,是在 Spark Streaming 層面添加對 Receiver 的分發目的地的計算,相對於之前版本依賴 Spark Core 的 TaskScheduler 進行通用分發,新的 ReceiverSchedulingPolicy 會對 Streaming 應用的更好的語義理解,也能計算出更好的分發策略。

ReceiverSchedulingPolicy 有兩個方法,分別用於:

  • 在 Streaming 程序首次啓動時:

    • 收集所有 InputDStream 包含的所有 Receiver 實例 —— receivers
    • 收集所有的 executor —— executors —— 作爲候選目的地
    • 然後就調用 ReceiverSchedulingPolicy.scheduleReceivers(receivers, executors) 來計算每個 Receiver 的目的地 executor 列表
  • 在 Streaming 程序運行過程中,如果需要重啓某個 Receiver

    • 將首先看一看之前計算過的目的地 executor 有沒有還 alive 的
    • 如果沒有,就需要 ReceiverSchedulingPolicy.rescheduleReceiver(receiver, ...) 來重新計算這個 Receiver 的目的地 executor 列表

默認的 ReceiverSchedulingPolicy 是實現爲 round-robin 式的了。我們舉例說明下這兩個方法:

其中,在 Receiver y 失效時,以前的 Spark Streaming 有可能會在 executor 1 上重啓 Receiver y,而 1.5.0 以來,將在 executor 3 上重啓 Receiver y

(2) 每個 Receiver 分發有單獨的 Job 負責

1.5.0 版本以來的 Spark Streaming,是爲每個 Receiver 都分配單獨的只有 1 個 Task 的 Job 來嘗試分發,這與以前版本將 x 個 Receiver 都放到一個有 x 個 Task 的 Job 裏分發是很不一樣的。

而且,對於這僅有的一個 Task,只在第 1 次執行時,才嘗試啓動 Receiver;如果該 Task 因爲失效而被調度到其它 executor 執行時,就不再嘗試啓動 Receiver、只做一個空操作,從而導致本 Job 的狀態是成功執行已完成。ReceiverTracker 會另外調起一個 Job —— 有可能會重新計算 Receiver 的目的地 —— 來繼續嘗試 Receiver 分發……如此直到成功爲止。

另外,由於 Spark Core 的 Task 下發時只會參考並大部分時候尊重 Spark Streaming 設置的 preferredLocation 目的地信息,還是有一定可能該分發 Receiver 的 Job 並沒有在我們想要調度的 executor 上運行。此時,在第 1 次執行 Task 時,會首先向 ReceiverTracker 發送 RegisterReceiver 消息,只有得到肯定的答覆時,才真正啓動 Receiver,否則就繼續做一個空操作,導致本 Job 的狀態是成功執行已完成。當然,ReceiverTracker 也會另外調起一個 Job,來繼續嘗試 Receiver 分發……如此直到成功爲止。

我們用圖示來表達這個改動:

所以通過上面可以看到,一個 Receiver 的分發 Job 是有可能沒有完成分發 Receiver 的目的的,所以 ReceiverTracker 會繼續再起一個 Job 來嘗試 Receiver 分發。這個機制保證了,如果一次 Receiver 如果沒有抵達預先計算好的 executor,就有機會再次進行分發,從而實現在 Spark Streaming 層面對 Receiver 所在位置更好的控制。

(3) 對 Receiver 的監控重啓機制

上面分析了每個 Receiver 都有專門的 Job 來保證分發後,我們發現這樣一來,Receiver 的失效重啓就不受 spark.task.maxFailures(默認=4) 次的限制了。

因爲現在的 Receiver 重試不是在 Task 級別,而是在 Job 級別;並且 Receiver 失效後並不會導致前一次 Job 失敗,而是前一次 Job 成功、並新起一個 Job 再次進行分發。這樣一來,不管 Spark Streaming 運行多長時間,Receiver 總是保持活性的,不會隨着 executor 的丟失而導致 Receiver 死去。

總結

我們再簡單對比一下 1.4.0 和 1.5.0 版本在 Receiver 分發上的區別:

 

通過以上分析,我們總結:

  Spark Streaming 1.4.0 Spark Streaming 1.5.0
Receiver 活性 不保證永活 無限重試、保證永活
Receiver 均衡分發 無保證 round-robin 策略
自定義 Receiver 分發 很 tricky 方便

 

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