Spark Streaming原理簡析

執行流程

數據的接收

StreamingContext實例化的時候,需要傳入一個SparkContext,然後指定要連接的spark matser url,即連接一個spark engine,用於獲得executor。

實例化之後,首先,要指定一個接收數據的方式,如

val lines = ssc.socketTextStream("localhost", 9999)

這樣從socket接收文本數據。這個步驟返回的是一個ReceiverInputDStream的實現,內含Receiver,可接收數據並轉化爲RDD放內存裏。

ReceiverInputDStream有一個需要子類實現的方法

def getReceiver(): Receiver[T]

子類實現這個方法,worker節點調用後能得到Receiver,使得數據接收的工作能分佈到worker上。

如果是local跑,由於Receiver接收數據在本地,所以在啓動streaming application的時候,要注意分配的core數目要大於Receiver數目,才能騰出cpu做計算任務的調度。

Receiver需要子類實現

def onStart()
def onStop()

來定義一個數據接收器的初始化、接收到數據後如何存、如何在結束的時候釋放資源。

Receiver提供了一系列store()接口,如store(ByteBuffer)store(Iterator)等等。這些store接口是實現好了的,會由worker節點上初始化的ReceiverSupervisor來完成這些存儲功能。ReceiverSupervisor還會對Receiver做監控,如監控是否啓動了、是否停止了、是否要重啓、彙報error等等。

ReceiverSupervisor的存儲接口的實現,藉助的是BlockManager,數據會以RDD的形式被存放,根據StorageLevel選擇不同存放策略。默認是序列化後存內存,放不下的話寫磁盤(executor)。被計算出來的RDD中間結果,默認存放策略是序列化後只存內存。

ReceiverSupervisor在做putBlock操作的時候,會首先借助BlockManager存好數據,然後往ReceiverTracker發送一個AddBlock的消息。ReceiverTracker內部的ReceivedBlockTracker用於維護一個receiver接收到的所有block信息,即BlockInfo,所以AddBlock會把信息存放在ReceivedBlockTracker裏。未來需要計算的時候,ReceiverTracker根據streamId,從ReceivedBlockTracker取出對應的block列表。

RateLimiter幫助控制Receiver速度,spark.streaming.receiver.maxRate參數。

數據源方面,普通的數據源爲file, socket, akka, RDDs。高級數據源爲Twitter, Kafka, Flume等。開發者也可以自己定製數據源。

任務調度

JobScheduler在context裏初始化。當context start的時候,觸發scheduler的start。

scheduler的start觸發了ReceiverTrackerJobGenerator的start。這兩個類是任務調度的重點。前者在worker上啓動Receiver接收數據,並且暴露接口能夠根據streamId獲得對應的一批Block地址。後者基於數據和時間來生成任務描述。

JobScheduler內含一個線程池,用於調度任務執行。spark.streaming.concurrentJobs可以控制job併發度,默認是1,即它只能一個一個提job。

job來自JobGenerator生成的JobSetJobGenerator根據時間,生成job並且執行cp。

JobGenerator的生成job邏輯:
- 調用ReceiverTrackerallocateBlocksToBatch方法,爲本批數據分配好block,即準備好數據
- 間接調用DStreamgenerateJob(time)方法,製造可執行的RDD

DStream切分RDD和生成可執行的RDD,即getOrCompute(time)
- 如果這個時間點的RDD已經生成好了,那麼從內存hashmap裏拿出來,否則下一步
- 如果時間是批次間隔的整數倍,則下一步,否則這個時間點不切
- 調用DStream的子類的compute方法,得到RDD。可能是一個RDD,也可以是個RDD列表
- 對每個RDD,調用persist方法,制定默認的存儲策略。如果時間點合適,同時調用RDD的checkpoint方法,制定好cp策略
- 得到這些RDD後,調用SparkContext.runJob(rdd, emptyFunction)。把這整個變成一個function,生成Job類。未來會在executor上觸發其runJob

JobGenerator成功生成job後,調用JobScheduler.submitJobSet(JobSet)JobScheduler會使用線程池提交JobSet中的所有job。該方法調用結束後,JobGenerator發送一個DoCheckpoint的消息,注意這裏的cp是driver端元數據的cp,而不是RDD本身的cp。如果time合適,會觸發cp操作,內部的CheckpointWriter類會完成write(streamingContext, time)

JobScheduler提交job的線程裏,觸發了job的run()方法,同時,job跑完後,JobScheduler處理JobCompleted(job)。如果job跑成功了,調用JobSethandleJobCompletion(Job),做些計時和數數工作,如果整個JobSet完成了,調用JobGeneratoronBatchCompletion(time)方法,JobGenerator接着會做clearMetadata的工作,然後JobScheduler打印輸出;如果job跑失敗了,JobScheduler彙報error,最後會在context裏拋異常。

更多說明

特殊操作

  1. transform:可以與外部RDD交互,比如做維表的join

  2. updateStateByKey:生成StateDStream,比如做增量計算。WordCount例子

    • 每一批都需要與增量RDD進行一次cogroup之後,然後執行update function。兩個RDD做cogroup過程有些開銷:RDD[K, V]和RDD[K, U]合成RDD[K, List[V], List[U]],List[U]一般size是1,理解爲oldvalue,即RDD[K, batchValueList, Option[oldValue]]。然後update function處理完,變成RDD[K, newValue]。
    • 批與批之間嚴格有序,即增量合併操作,是有序的,批之間沒發併發
    • 增量RDD的分區數可以開大,即這步增量的計算可以調大併發
  3. window:batch size,window length, sliding interval三個參數組成的滑窗操作。把多個批次的RDD合併成一個UnionRDD進行計算。

  4. foreachRDD: 這個操作是一個輸出操作,比較特殊。

  /**
   * Apply a function to each RDD in this DStream. This is an output operator, so
   * 'this' DStream will be registered as an output stream and therefore materialized.
   */
  def foreachRDD(foreachFunc: (RDD[T], Time) => Unit) {
    new ForEachDStream(this, context.sparkContext.clean(foreachFunc, false)).register()
  }

DStream.foreachRDD()操作使開發者可以直接控制RDD的計算邏輯,而不是通過DStream映射過去。所以藉助這個方法,可以實現MLlib, Spark SQL與Streaming的集合,如:結合Spark SQL、DataFrame做Wordcount

Cache

如果是window操作,默認接收的數據都persist在內存裏。

如果是flume, kafka源頭,默認接收的數據replicate成兩份存起來。

Checkpoint

與state有關的流計算,計算出來的結果RDD,會被cp到HDFS上,原文如下:

Data checkpointing - Saving of the generated RDDs to reliable storage. This is necessary in some stateful transformations that combine data across multiple batches. In such transformations, the generated RDDs depends on RDDs of previous batches, which causes the length of the dependency chain to keep increasing with time. To avoid such unbounded increase in recovery time (proportional to dependency chain), intermediate RDDs of stateful transformations are periodically checkpointed to reliable storage (e.g. HDFS) to cut off the dependency chains.

cp的時間間隔也可以設定,可以多批做一次cp。

cp的操作是同步的。

簡單的不帶state操作的流任務,可以不開啓cp。

driver端的metadata也有cp策略。driver cp的時候是將整個StreamingContext對象寫到了可靠存儲裏。

全文完 :)

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