Spark 定製版:009~Spark Streaming源碼解讀之Receiver在Driver的精妙實現全生命週期徹底研究和思考

本講內容:

a. Receiver啓動的方式設想
b. Receiver啓動源碼徹底分析

注:本講內容基於Spark 1.6.1版本(在2016年5月來說是Spark最新版本)講解。

上節回顧

上一講中,我們給大傢俱體分析了RDD的物理生成和邏輯生成過程,徹底明白DStream和RDD之間的關係,及其內部其他有關類的具體依賴等信息:

a. DStream是RDD的模板,其內部generatedRDDs 保存了每個BatchDuration時間生成的RDD對象實例。DStream的依賴構成了RDD依賴關係,即從後往前計算時,只要對最後一個DStream計算即可。

b. JobGenerator每隔BatchDuration調用DStreamGraph的generateJobs方法,調用了ForEachDStream的generateJob方法,其內部先調用父DStream的getOrCompute方法來獲取RDD,然後在進行計算,從後往前推,第一個DStream是ReceiverInputDStream,其comput方法中從receiverTracker中獲取對應時間段的metadata信息,然後生成BlockRDD對象,並放入到generatedRDDs中

開講

由上幾節課中我們知道:

a. Spark Streaming的應用程序在處理數據時,會在開始的階段做好接收數據的準備

b. Spark Streaming的應用程序代碼定義DStream時,會定義一個或多個InputDStream;而每個InputDStream則分別對應有一個Receiver

結合源碼的具體類和方法繪製Receiver啓動全生命週期主流程圖:

(原圖信息來自http://blog.csdn.net/andyshar/article/details/51476113,感謝作者!)
這裏寫圖片描述

我們就從本講的內容開始,爲大家解析:

那麼Receiver啓動的方式設想到底是什麼呢?

Receiver啓動的設計問題分析:
 
a. Spark Streaming通過Receiver持續不斷的從外部數據源接收數據,並把數據彙報給Driver端,由此每個Batch Durations就可以根據彙報的數據生成不同的Job

b. 即有可能在同一個Executor之中啓動多個Receiver,這種情況之下導致負載不均勻

c. 由於Executor運行本身的故障,task 有可能啓動失敗,整個job啓動就失敗,即receiver啓動失敗

d. Receiver屬於Spark Streaming應用程序啓動階段,它又是如何設計,來達到Receiver始終會被啓動

e. Receivers和InputDStreams又是如何一一對應的,默認情況下一般只有一個Receiver嗎?

來吧,走進源碼一起看個究竟!!

Receiver啓動源碼徹底分析:

如何啓動Receiver?

a. 從Spark Core的角度來看,Receiver的啓動Spark Core並不知道, Receiver是通過Job的方式啓動的,運行在Executor之上的,由task運行

b. 一般情況下,只有一個Receiver,但是可以創建不同的數據來源的InputDStream

c. 啓動Receiver的時候,實際上一個receiver就是一個partition,並由一個Job啓動,這個Job裏面有RDD的transformations操作和action的操作,隨着定時器觸發,不斷的產生有數據接收,每個時間段中產生的接收數據實際上就是一個partition

如此,又回到了最初Receiver啓動的方式設想中的問題:

a. 如果有多個InputDStream,那就要啓動多個Receiver,每個Receiver也就相當於分片partition,那我啓動Receiver的時候理想的情況下是在不同的機器上啓動Receiver,但是Spark Core的角度來看就是應用程序,感覺不到Receiver的特殊性,所以就會按照正常的Job啓動的方式來處理,極有可能在一個Executor上啓動多個Receiver;這樣的話就可能導致負載不均衡

b. 有可能啓動Receiver失敗,只要集羣存在,Receiver就不應該啓動失敗

c. 從運行過程中看,一個Reveiver就是一個partition的話,Reveiver的啓動伴隨一個Task啓動,如果Task啓動失敗,以Task啓動的Receiver也會失敗

由此,可以得出,對於Receiver失敗的話,後果是非常嚴重的,那麼在Spark Streaming如何防止這些事的呢?

Spark Streaming源碼分析,在Spark Streaming之中就指定如下信息:

a. Spark使用一個Job啓動一個Receiver.最大程度的保證了負載均衡

b. Spark Streaming已經指定每個Receiver運行在那些Executor上,在Receiver運行之前就指定了運行的地方

c. 如果Receiver啓動失敗,此時並不是Job失敗,在內部會重新啓動Receiver

進入到StreamingContext源碼,開啓解密之旅吧!

在StreamingContext的start方法被調用的時候,JobScheduler的start方法會被調用

scheduler.start()://啓動子線程,一方面爲了本地初始化工作,另外一方面是不要阻塞主線程

這裏寫圖片描述

而在JobScheduler的start方法中ReceiverTracker的start方法被調用,Receiver就啓動了

這裏寫圖片描述

ReceiverTracker的start方法啓動RPC消息通信體,爲啥呢?因爲receiverTracker會監控整個集羣中的Receiver,Receiver轉過來要向ReceiverTrackerEndpoint彙報自己的狀態,接收的數據,包括生命週期等信息

這裏寫圖片描述

基於ReceiverInputDStream(是在Driver端)來獲得具體的Receivers實例,然後再把他們分佈到Worker節點上。一個ReceiverInputDStream只對應一個Receiver

這裏寫圖片描述

其中runDummySparkJob()爲了確保所有節點活着,而且避免所有的receivers集中在一個節點上

這裏寫圖片描述

再回去看ReceiverTracker.launchReceivers()中的getReceiver()

這裏寫圖片描述

ReceiverInputDStream的getReceiver()方法返回Receiver對象。 該方法實際上要在ReceiverInputDStream的子類實現。

相應的,ReceiverInputDStream的子類中必須要實現這個getReceiver()方法。ReceiverInputDStream的子類還必須定義自己對應的Receiver子類,因爲這個Receiver子類會在getReceiver()方法中用來創建這個Receiver子類的對象。

因此,我們需要查看以下ReceiverInputDStream的繼承關係

這裏寫圖片描述

根據繼承關係,這裏看一下ReceiverInputDStream的子類SocketInputDStream中的getReceiver方法

這裏寫圖片描述

SocketInputDStream中還定義了相應的Receiver子類SocketReceiver。SocketReceiver類中還必須定義onStart方法

onStart方法會啓動後臺線程,調用receive方法

這裏寫圖片描述

啓動socket開始接收數據

這裏寫圖片描述

再回到ReceiverTracker.launchReceivers()中,看最後的代碼 endpoint.send(StartAllReceivers(receivers))。這個代碼給ReceiverTrackerEndpoint對象發送了StartAllReceivers消息,ReceiverTrackerEndpoint對象接收後所做的處理在ReceiverTrackerEndpoint.receive中。

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

從註釋中可以看到,Spark Streaming指定receiver在哪些Executors上運行,而不是基於Spark Core中的Task來指定

Spark使用submitJob的方式啓動Receiver,而在應用程序執行的時候會有很多Receiver,這個時候是啓動一個Receiver呢,還是把所有的Receiver通過這一個Job啓動?

在ReceiverTracker的receive方法中startReceiver方法第一個參數就是receiver,從實現中可以看出for循環不斷取出receiver,然後調用startReceiver。由此就可以得出一個Job只啓動一個Receiver
  
如果Receiver啓動失敗,此時並不會認爲是作業失敗,會重新發消息給ReceiverTrackerEndpoint重新啓動Receiver,這樣也就確保了Receivers一定會被啓動,這樣就不會像Task啓動Receiver的話如果失敗受重試次數的影響。

ReceiverTracker.startReceiver:

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

當Receiver啓動失敗的話,就會觸發ReceiverTrackEndpoint重新啓動一個Spark Job去啓動Receiver

這裏寫圖片描述

當Receiver關閉的話,並不需要重新啓動Spark Job

這裏寫圖片描述

回頭再看ReceiverTracker.startReceiver中的代碼supervisor.start()。在子類ReceiverSupervisorImpl中並沒有start方法,因此調用的是父類ReceiverSupervisor的start方法。

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

其具體實現是在子類的ReceiverSupervisorImpl的onStart方法:

這裏寫圖片描述

其中的_.start()是BlockGenerator.start:

這裏寫圖片描述

回過頭再看ReceiverSupervisor.start中的startReceiver()

這裏寫圖片描述

這裏寫圖片描述

仍以Receiver的子類SocketReceiver爲例說明onStart方法

SocketReceiver.onStart:

這裏寫圖片描述

這個onStart方法開啓了的線程,用於啓動socket來接收數據。這個被運行的receive()被定義在ReceiverInputDStream的子類SocketInputDStream中

這裏寫圖片描述

這裏寫圖片描述

發佈了42 篇原創文章 · 獲贊 11 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章