原創(chuàng)文章,轉(zhuǎn)載請注明:轉(zhuǎn)載自聽風(fēng)居士博客(http://www.lxweimin.com/users/4435a13863fb/timeline)
Spark streaming 程序需要不斷接收新數(shù)據(jù),然后進行業(yè)務(wù)邏輯處理,而用于接受數(shù)據(jù)的就是Recever。顯然Receiver的正常運行對應(yīng)整個Spark Streaming應(yīng)用程序至關(guān)重要,如果Receiver出現(xiàn)異常,后面的業(yè)務(wù)邏輯就無從談起。Spark Streaming 是如何實現(xiàn)Receiver以保證其可靠性的,本文將結(jié)合Spark Streaming的Receiver源碼實現(xiàn)詳細解析Receiver的實現(xiàn)原理。
一、Receiver 實現(xiàn)策略思考
1、啟動Receiver的時候,啟動一個Job,這個Job里面有RDD的transformations操作和action的操作,這個Job只有一個partition.這個partition的特殊是里面只有一個成員,這個成員就是啟動的Receiver。這樣做的問題:
a) ?如果有多個InputDStream,那就要啟動多個Receiver,每個Receiver也就相當于分片partition,那我們啟動Receiver的時候理想的情況下是在不同的機器上啟動Receiver,但是Spark Core的角度來看就是應(yīng)用程序,感覺不到Receiver的特殊性,所以就會按照正常的Job啟動的方式來處理,極有可能在一個Executor上啟動多個Receiver.這樣的話就可能導(dǎo)致負載不均衡。
b) ?有可能啟動Receiver失敗,只要集群存在Receiver就不應(yīng)該失敗。
c) ?運行過程中,就默認的而言如果是一個partition的話,那啟動的時候就是一個Task,但是此Task也很可能失敗,因此以Task啟動的Receiver也會掛掉。
2、由Spark Streaming 自己管理Receiver,負責Receiver的調(diào)度和容錯和啟動。這樣做的好處:
a)由Spark Streaming調(diào)度Receiver 可以充分考慮負責均衡,避免將多個Receiver調(diào)度到同一臺機器上
b)Receiver 失敗后可以自動重新啟動,繼續(xù)接受數(shù)據(jù),從而使程序持續(xù)不斷繼續(xù)工作下去。
c)Receiver 重啟不收Task重啟次數(shù)的限制。
二、Spark Streaming的Receiver實現(xiàn)原理
2.1、和Receiver實現(xiàn)相關(guān)核心成員
(1)ReceiverTracker
(2)ReceiverTrackerEndpoint
(3)ReceiverSuperVisor
(4)Receiver
ReceiverTracker在Driver端,ReceiverSuperVisor和Receiver在Executor端,架構(gòu)圖如下:
2.2Spark Streaming的Receiver實現(xiàn)源碼解析
首先SparkStream啟動時候會啟動JobScheduler,在JobSceduler的start方法中,會實例化ReceiverTracker,并調(diào)用ReceiverTracker的start方法啟動ReceiverTracker。
ReceiverTracker的start方法首先檢查輸入流是否為空,如果不為空會創(chuàng)建ReceiverTrackerEndpoint并注冊給rpcEnv。然后調(diào)用launchReceivers方法啟動Receiver。其中receiverInputStreams是注冊到DStreamGraph中的ReceiverInputDStream。
下面看一下launchReceiver方法:基于ReceiverInputDStream(是在Driver端)來獲得具體的Receivers實例,然后再把他們分不到Worker節(jié)點上。一個ReceiverInputDStream只產(chǎn)生一個Receiver
首先從ReceiverInputDStream中獲取Receiver,然后調(diào)用runDummySparkJob啟動一個虛擬任務(wù),我們在后面再分析這個虛擬任務(wù),先看一下后面的核心代碼:
endpoint.send(StartAllReceivers(receivers))
此處的endpoint就是剛才實例化的ReceiverTrackerEndpoint對象的引用,可以看到此處給endpoint發(fā)送了StartAllReceivers消息。
下面看下一ReceiverTrackerEndpoint收到StartAllReceivers消息后的處理邏輯:
首先,根據(jù)一定的調(diào)度策略給傳入receivers分配相應(yīng)的executors,從這里可以看出,Receiver的調(diào)度并不是交給spark內(nèi)核完成的,而是由Spark Streaming框架完成調(diào)度過程。這樣做的目的就是為了避免Spark內(nèi)核將Receiver當做普通的job而將多個Receiver調(diào)度到同一個節(jié)點上。
Spark Streaming的調(diào)度策略這里不做分析,接著看下面的代碼,迭代所以的receiver ,對每個receiver調(diào)用 startReceiver方法在具體Executor上啟動Receiver。
這個startReceiver比較復(fù)雜,我們一步步分析,先看最核心的一行代碼:
可看到,Spark Streaming 為每個Receiver 啟動了一個job,而不是由Action操作出發(fā)Job執(zhí)行。
這里job的提交主要關(guān)注兩個參數(shù)receiverRDD和startReceiverFunc。
receiverRDD的源碼:
可以看到調(diào)用了SparkContext的makeRDD方法創(chuàng)建了RDD,該RDD只有一條數(shù)據(jù),就是receiver對象
下面看一看startReceiverFunc的源碼
startReceiverFunc 在worker節(jié)點上啟動receiver,首先創(chuàng)建了一個ReceiverSupervisiorImpl 對象 supervisor,然后調(diào)用supervisor的start方法在該節(jié)點上啟動supervisor:
ReceiverSupervisiorImpl 是繼承自ReceiverSupervisor,ReceiverSupervisor中調(diào)用了startReceiver方法:
首先調(diào)用onReceiverStart方法,將Receiver注冊給receiverTracker:
如果注冊成功,調(diào)用了Receiver的onStart方法在Executor啟動Receiver不斷接受數(shù)據(jù),并將接收的數(shù)據(jù)交給BlockManager管理,至此Receiver啟動完成。
回到ReceiverTracker的startReceiver方法,如果Receiver對應(yīng)的job完成,無論返回成功或失敗,只要ReceiverTracker還沒有停止就會發(fā)送RestartReceiver消息給ReceiverTrakerEndpoint,重啟Receiver。從這里可以看出Receiver不會像普通的spark core 程序一樣受到重試次數(shù)的限制而導(dǎo)致作業(yè)失敗
最后,在看一下runDummyJob方法:
該方法運行了一個簡單的wordcount程序,運行該程序的目的是確保所有slaves節(jié)點都被注冊了,讓receiver盡量分配到不同的work上運行,看一下getExecutors的源碼 :
總結(jié):Driver端的ReceiverTracker管理所有Executor上的Receiver任務(wù),他有一個ReceiverTrakerEndpoint 消息通訊體,這個消息通訊體在startReceiver方法中提交Receiver的job在具體Executor上運行,并接受Executor端發(fā)送過來的消息(比如注冊Receiver),在Executor端有一個ReceiverSupervisor專門管理Receiver,負責Receiver的注冊啟動與ReceiverTracker的信息交互。