本講內容:
a. 解密Spark Streaming運行機制
b. 解密Spark Streaming架構
注:本講內容基於Spark 1.6.1版本(在2016年5月來說是Spark最新版本)講解。
上節回顧:
上節課談到技術界的尋龍點穴,Spark就是大數據的龍脈,而Spark Streaming就是Spark的穴位。假如要構建一個強大的Spark應用程序 ,Spark Streaming 是一個值得借鑑的參考,Spark Streaming涉及多個job交叉配合,幾乎可以包括spark的所有的核心組件,如果對Spark Streaming精通了的話,可以說就精通了整個Spark,因此精通並掌握Spark Streaming是至關重要的。
在Spark官網(這裏寫鏈接內容)中,可以看到如下圖所示:
Spark Core上面有4個流行的框架:Spark SQL、Spark Streaming、機器學習、圖計算。除了Spark Streaming,其他的子框架大多都是在Spark Core上對一些算法或者接口進行了高層的封裝。例如Spark SQL 封裝了SQL語法,主要功能就是將SQL語法解析成Spark Core的底層API。而機器學習則是封裝了許多的數學向量及算法。GraphX目前也沒有太大的更新。
而Spark Streaming更像是對Spark Core的衍生子框架,可想而知,他是相當複雜的一個應用程序。同時我們也不難發現,基於Spark Core的時候,都是基於RDD編程,Spark Streaming則是基於DStream編程。DStream是邏輯級別的,而RDD是物理級別的。DStream是隨着時間的流動內部將集合封裝RDD。對DStream的操作,歸根結底還是對其RDD進行的操作。
我們查看上一節中Spark Streaming的運行日誌,就可以看出和RDD的運行幾乎是一致的:
SparkStreaming Job在運行的時候,首先會生成DStream的Graph,在特定的時間將DStream Graph轉換成RDD Graph。然後再去運行RDD的job 。如下圖:
從這個角度來講,可以將Spark Streaming放在座標系中。其中Y軸就是對RDD的操作,RDD的依賴關係構成了整個job的邏輯,而X軸就是時間。隨着時間的流逝,固定的時間間隔(Batch Interval)就會生成一個job實例,進而在集羣中運行。
由此爲大家詳細總結並揭祕 Spark Streaming五大核心特徵
特徵1:邏輯管理
DStream是對RDD封裝的集合,作用於DStream的操作會對其中每個RDD進行作用,DStream Graph就是RDD Graph的模板,其邏輯管理完全繼承RDD的DAG關係。
特徵2:時間管理
Spark Streaming的最大特徵是引入了時間屬性,DStream在RDD的基礎上增加了時間緯度,隨着時間的緯度,不斷把模板實例化,通過動態Job控制器運行作業。
特徵3:流式輸入和輸出
以InputStream和OutputStream爲核心,進行流式的數據輸入輸出。
特徵4:高容錯
具體Job運行在Spark Cluster之上,此時系統容錯就至關重要。
主要思路:
a、限流
b、根據需要調整資源安排
特徵5:事務處理
在處理出現崩潰的情況下確保Exactly once的事務語義。主要通過檢查點等技術實現。(下一講再細說)
結合Spark Streaming源碼進一步解析:
StreamingContext方法中調用JobScheduler的start方法(StreamingContext.scala,610行代碼)
JobGenerator的start方法中,調用startFirstTime方法,來開啓定時生成Job的定時器
(JobScheduler.scala,83行代碼)
(JobGenerator.scala,98行代碼)
startFirstTime方法,首先調用DStreamGraph的start方法,然後再調用RecurringTimer的start方法。
(JobGenerator.scala,193行代碼)
(DStreamGraph.scala,39行代碼)
(RecurringTimer.scala,59行代碼)
timer對象爲一個定時器,根據batchInterval時間間隔定期向EventLoop發送GenerateJobs的消息。
(JobGenerator.scala,58~59行代碼)
接收到GenerateJobs消息後,會回調generateJobs方法。
(JobGenerator.scala,181行代碼)
generateJobs方法再調用DStreamGraph的generateJobs方法生成Job
(JobGenerator.scala,248行代碼)
(DStreamGraph.scala,248行代碼)
DStreamGraph的實例化是在StreamingContext中的
(StreamingContext.scala,162~164行代碼)
在DStreamGraph的類中還保存了輸入流和輸出流的信息
(DStreamGraph.scala,29~30行代碼)
DStream類中依賴、計算、保存RDD信息
(DStream.scala,74、77、85行代碼)
回到JobScheduler的start方法中receiverTracker.start()
(JobScheduler.scala,82行代碼)
(ReceiverTracker.scala,149行代碼)
其中ReceiverTrackerEndpoint對象爲一個消息循環體
(ReceiverTracker.scala,449行代碼)
launchReceivers方法中發送StartAllReceivers消息
(ReceiverTracker.scala,413行代碼)
接收到StartAllReceivers消息後,進行如下處理
(ReceiverTracker.scala,454行代碼)
(ReceiverTracker.scala,594行代碼)
StartReceiverFunc方法如下,實例化Receiver監控者,開啓並等待退出
(ReceiverTracker.scala,573行代碼)
supervisor的start方法中調用startReceiver方法
(ReceiverSupervisor.scala,130行代碼)
(ReceiverSupervisor.scala,148行代碼)
舉例解析:
以socketTextStream爲例,其啓動的是SocketReceiver,內部開啓一個線程,來接收數據
(SocketInputDStream.scala,55~60行代碼)
(SocketInputDStream.scala,73、77行代碼)
store(iterator.next)內部調用Receiver中的store方法裏supervisor的pushSingle方法,最終將數據聚集後存放在內存中
(Receiver.scala,73、77行代碼)
supervisor的pushSingle方法如下,將數據放入到defaultBlockGenerator中,defaultBlockGenerator爲BlockGenerator,保存Socket接收到的數據
(ReceiverSupervisorImpl.scala,118行代碼)
(BlockGeneratorListener.scala,162、165行代碼)
BlockGenerator對象中有一個定時器,來更新當前的Buffer
(BlockGenerator.scala,106行代碼)
(BlockGenerator.scala,241、246行代碼)
BlockGenerator對象中有一個線程,來從阻塞隊列中取出數據
(BlockGenerator.scala,109行代碼)
(BlockGenerator.scala,278行代碼)
(BlockGenerator.scala,296行代碼)
接下來會調用ReceiverSupervisorImpl類中的繼承BlockGeneratorListener的匿名類中的onPushBlock方法
(ReceiverSupervisorImpl.scala,108行代碼)
(ReceiverSupervisorImpl.scala,123、128行代碼)
(ReceiverSupervisorImpl.scala,157、161行代碼)
receivedBlockHandler對象如下
(ReceiverSupervisorImpl.scala,53~64行代碼)
這裏我們講解BlockManagerBasedBlockHandler的方式
(BlockManagerBasedBlockHandler.scala,74、79、81行代碼)
trackerEndpoint如下
(ReceiverSupervisorImpl.scala,70行代碼)
(RpcUtils.scala,31行代碼)
其實是發送給ReceiverTrackerEndpoint類
(ReceiverTracker.scala,156行代碼)
(ReceiverTracker.scala,495、507行代碼)
(ReceivedBlockTracker.scala,90行代碼)
(ReceiverInputDStream.scala,69、81、85行代碼)
InputInfoTracker類的reportInfo方法只是對數據進行記錄統計
(InputInfoTracker.scala,64行代碼)
(DStreamGraph.scala,442、446、448行代碼)
(DStreamGraph.scala,352行代碼)
其generateJob方法是被DStreamGraph調用
(DStreamGraph.scala,115行代碼)
DStreamGraph的generateJobs方法是被JobGenerator類的generateJobs方法調用
(JobGenerator.scala,248行代碼)
JobGenerator類中有一個定時器,batchInterval發送GenerateJobs消息
(JobGenerator.scala,59行代碼)
源碼解密總結:
a,當調用StreamingContext的start方法時,啓動了JobScheduler
b,當JobScheduler啓動後會先後啓動ReceiverTracker和JobGenerator
c,ReceiverTracker啓動後會創建ReceiverTrackerEndpoint這個消息循環體,來接收運行在Executor上的Receiver發送過來的消息
d,ReceiverTracker在啓動時會給自己發送StartAllReceivers消息,自己接收到消息後,向Spark提交startReceiverFunc的Job
e,startReceiverFunc方法中在Executor上啓動Receiver,並實例化ReceiverSupervisorImpl對象,來監控Receiver的運行
f,ReceiverSupervisorImpl對象會調用Receiver的onStart方法,我們以SocketReceiver爲例,啓動一個線程,連接Server,讀取網絡數據先調用ReceiverSupervisorImpl的pushSingle方法,
保存在BlockGenerator對象中,該對象內部有個定時器,放到阻塞隊列blocksForPushing,等待內部線程取出數據放到BlockManager中,併發AddBlock消息給ReceiverTrackerEndpoint。
ReceiverTrackerEndpoint爲ReceiverTracker的內部類,在接收到addBlock消息後將streamId對應的數據阻塞隊列streamIdToUnallocatedBlockQueues中
g,JobGenerator啓動後會啓動以batchInterval時間間隔發送GenerateJobs消息的定時器
h,接收到GenerateJobs消息會先後觸發ReceiverTracker的allocateBlocksToBatch方法和DStreamGraph的generateJobs方法
i,ReceiverTracker的allocateBlocksToBatch方法會調用getReceivedBlockQueue方法從阻塞隊列streamIdToUnallocatedBlockQueues中根據streamId獲取數據
j,DStreamGraph的generateJobs方法,繼而調用變量名爲outputStreams的DStream集合的generateJob方法
k,繼而調用DStream的getOrCompute來調用具體的DStream的compute方法,我們以ReceiverInputDStream爲例,compute方法是從ReceiverTracker中獲取數據
經典直說:
在空間維度上的業務邏輯作用於DStream,隨着時間的流逝,每個Batch Interval形成了具體的數據集,產生了RDD,對RDD進行transform操作,進而形成了RDD的依賴關係RDD DAG,形成job。然後jobScheduler根據時間調度,基於RDD的依賴關係,把作業發佈到Spark Cluster上去運行,不斷的產生Spark作業。