概述
Spark Streaming 是 Spark Core API 的擴展, 它支持彈性的, 高吞吐的, 容錯的實時數據流的處理. 數據可以通過多種數據源獲取, 例如 Kafka, Flume, Kinesis 以及 TCP sockets, 也可以通過例如 map
, reduce
, join
, window
等的高級函數組成的複雜算法處理. 最終, 處理後的數據可以輸出到文件系統, 數據庫以及實時儀表盤中. 事實上, 你還可以在 data streams(數據流)上使用 機器學習 以及 圖計算算法.
運行原理
- sparkStreaming不斷的從Kafka等數據源獲取數據(連續的數據流),並將這些數據按照週期劃分成爲batch
- 將每個batch的數據提交給SparkEngine來處理(每個batch的處理實際上還是批處理,只不過批量很小,幾乎解決了實時處理)
- 整個過程是持續的,即不斷的接收數據並處理數據和輸出結果
DStream
- DStream : Discretized Stream 離散流
- 爲了便於理解,Spark Straming提出了DStream對象,代表一個連續不斷的輸入流
- DStream是一個持續的RDD序列,每個RDD代表一個計算週期(DStream裏面有多個RDD)
- 所有應用在DStream上的操作,都會被映射爲對DStream內部的RDD上的操作
- DStream本質上是一個以時間爲鍵,RDD爲值的哈希表,保存了按時間順序產生的RDD,。Spark Streaming每次將新產生的RDD添加到哈希表中,而對於已經不再需要的RDD則會從這個哈希表中刪除,所以DStream也可以簡單地理解爲以時間爲鍵的RDD的動態序列,。設批處理時間間隔爲1s,下圖爲4s內產生的DStream示意圖。
初始化注意點:
- 一旦一個 context 已經啓動,將不會有新的數據流的計算可以被創建或者添加到它。.
- 一旦一個 context 已經停止,它不會被重新啓動.
- 同一時間內在 JVM 中只有一個 StreamingContext 可以被激活.
- 在 StreamingContext 上的 stop() 同樣也停止了 SparkContext 。爲了只停止 StreamingContext ,設置
stop()
的可選參數,名叫stopSparkContext
爲 false. - 一個 SparkContext 就可以被重用以創建多個 StreamingContexts,只要前一個 StreamingContext 在下一個StreamingContext 被創建之前停止(不停止 SparkContext).
DStream輸入源
- Basic sources
- file systems:
sparkContext.textFileStream(dir)
- 只監控指定文件夾中的文件,不監控裏面的文件夾
- 以文件的修改時間爲準
- 一旦開始處理,對文件的修改在當前窗口不會被讀取
- 文件夾下面文件越多掃描時間越長(和文件是否修改無關)
- hdfs在打開輸出流的時候就設置了更新時間,這個時候write操作還未完成就被讀,可以先將文件寫到一個未被監控的文件夾,待write 操作完成後,再移入監控的文件夾中
- socket connections:
sparkContext.socketTextStream()
- Akka actors
- file systems:
- Advanced sources
- Kafka:
KafkaUtils.createStream(ssc,zkQuorum,group,topicMap)
- Flume
- Kinesis
- Kafka:
- multiple input stream
- ssc.union(Seq(stream1,stream2,...))
- stream1.union(stream2)
Batch duration
對於源源不斷的數據,Spark Streaming是通過切分的方式,先將連續的數據流進行離散化處理。數據流每被切分一次,對應生成一個RDD,每個RDD都包含了一個時間間隔內所獲取到的所有數據。
批處理時間間隔的設置會伴隨Spark Streaming應用程序的整個生命週期,無法在程序運行期間動態修改
- duration設置:
new StreamingContext(sparkConf,Seconds(1))
- Spark Streaming按照設置的batch duration來積累數據,週期結束時把週期內的數據作爲一個RDD,並添加任務給Spark Engine
- batch duration的大小決定了Spark Streaming提交作業的頻率和處理延遲
- batch duration大小設定取決於用戶需求,一般不會太大
Receiver接收器
- 除了FileInputDStream,其餘輸入源都會關聯一個Receiver。
- receiver以任務的形式運行在應用的執行器進程中,從輸入源收集數據並保存爲RDD。
- receiver會將接收到的數據複製到另一個工作節點上進行加工處理。
- core的最小數量是2,一個負責接收,一個負責處理(fileSysInput除外)
- 分配給處理數據的cores應該多餘分配給receivers的數量
轉換操作
- 無狀態操作
- 和spark core語義一致
- 對DStream的transform操作,實際作用於DStream中的每一個RDD
- 如果DStream沒有提供RDD操作,可通過transform函數實現,
dstream.transform(fun)
- 不能跨多個batch中的RDD執行
- 有狀態操作
- updateStateByKey :定一個一個狀態和更新函數,返回新的狀態,updateStateByKey必須配置檢查點
- window: 流式計算是週期性進行的,有時處理處理當前週期的數據,還需要處理最近幾個週期的數據,這時候就需要窗口操作方法了。我們可以設置數據滑動窗口,將數個原始Dstream合併成一個窗口DStream。window操作默認執行persist in mermory
-
- windowDuration: 窗口時間間隔又稱爲窗口長度,它是一個抽象的時間概念,決定了Spark Streaming對RDD序列進行處理的範圍與粒度,即用戶可以通過設置窗口長度來對一定時間範圍內的數據進行統計和分析
- bathDuration: batch大小
- 每次計算的batch數:
windowDuration/batchDuration
- slideDuration: 滑動時間間隔,控制多長時間計算一次默認和batchDuration相等
操作規約
普通規約是每次把window裏面每個RDD都計算一遍,增量規約是每次只計算新進入window的數據,然後減去離開window的數據,得到的就是window數據的大小,在使用上,增量規約需要提供一個規約函數的逆函數,比如+
對應的逆函數爲-
-
普通規約:
val wordCounts=words.map(x=>(x,1)).reduceByKeyAndWindow(_+_,Seconds(5s),seconds(1))
-
增量規約:
val wordCounts=words.map(x=>(x+1)).reduceByKeyAndWindow(_+_,_-_,Seconds(5s),seconds(1))
DStream輸出
- 輸出操作:print,foreachRDD,saveAsObjectFiles,saveAsTextFiles,saveAsHadoopFiles
- 碰到輸出操作時開始計算求值
- 輸出操作特點:惰性求值
- 最佳建立鏈接的方式
// 1. con't not create before foreachPartition function(cont't create in driver)
// 2. use foreachPartition instead of foreach
// 3. use connect pool instead of create connect every time
dstream.foreachRDD { rdd =>
rdd.foreachPartition { partitionOfRecords =>
// ConnectionPool is a static, lazily initialized pool of connections
val connection = ConnectionPool.getConnection()
partitionOfRecords.foreach(record => connection.send(record))
ConnectionPool.returnConnection(connection) // return to the pool for future reuse
}
}
DSream持久化
- 默認持久化:
MEMORY_ONLY_SER
- 對於來源於網絡的數據源(kafka,flume等):
MEMORY_AND_DISK_SER_2
- 對於window操作默認進行
MEMORY_ONLY
持久化
checkpoint容錯
sparkStreaming 週期性的把應用數據存儲到HDFS等可靠的存儲系統中可以供回覆時使用的機制叫做檢查點機制,
作用:
- 控制發生失敗時需要計算的狀態數:通過lineage重算,檢查點機制可以控制需要在Lineage中回溯多遠
- 提供驅動器程序(driver)的容錯:可以重新啓動驅動程序,並讓驅動程序從檢查點恢復,這樣spark streaming就可以讀取之前運行的程序處理數據的進度,並從哪裏開始繼續。
數據類型:
-
Metadata(元數據): streaming計算邏輯,主要來恢復driver。
-
Configuration
:配置文件,用於創建該streaming application的所有配置 -
DStream operations
:對DStream進行轉換的操作集合 -
Incomplete batches
:未完成batchs,那些提交了job在隊列等待尚未完成的job信息。
-
-
Data checkpointing
: 已經生成的RDD但還未保存到HDFS或者會影響後續RDD的生成。
注意點
- 對於window和stateful操作必須指定checkpint
- 默認按照batch duration來做checkpoint
Checkpoint類
checkpoint的形式是將類CheckPoint的實例序列化後寫入外部內存
缺點
SparkStreaming 的checkpoint機制是對CheckPoint對象進行序列化後的數據進行存儲,那麼SparkStreaming Application重新編譯後,再去反序列化checkpoint數據就會失敗,這個時候必須新建StreamingContext
針對這種情況,在結合SparkStreaming+kafka的應用中,需要自行維護消費offsets,這樣即使重新編譯了application,還是可以從需要的offsets來消費數據。對於其他情況需要結合實際的需求進行處理。
使用
checkpoint的時間間隔正常情況下應該是sliding interval的5-10倍,可通過dstream.checkpoint(checkpointInterval)
配置每個流的interval。
如果想要application能從driver失敗中恢復,則application需要滿足
- 若application首次重啓,將創建一個新的StreamContext實例
- 若application從失敗中重啓,將會從chekcpoint目錄導入chekpoint數據來重新創建StreamingContext實例
def createStreamingContext()={
...
val sparkConf=new SparkConf().setAppName("xxx")
val ssc=new StreamingContext(sparkConf,Seconds(1))
ssc.checkpoint(checkpointDir)
}
...
val ssc=StreamingContext.getOrCreate(checkpointDir,createSreamingContext _)
Accumulators, Broadcast Variables, and Checkpoints
在sparkStreaming中累加器和廣播變量不能夠在checkpoints中恢復,廣播變量是在driver上執行的,但是當driver重啓後並沒有執行廣播,當slaves調用廣播變量時報Exception: (Exception("Broadcast variable '0' not loaded!",)
可以爲累加器和廣播變量創建延遲實例化的單例實例,以便在驅動程序重新啓動失敗後重新實例化它們
問題參考:https://issues.apache.org/jira/browse/SPARK-5206
容錯
系統的容錯主要從三個方面,接收數據,數據處理和輸出數據,在sparkStreaming中,接收數據和數據來源有關係,處理數據可以保證exactly once,輸出數據可以保證at least once。
輸入容錯
sparStreaming並不能完全的像RDD那樣實現lineage,因爲其有的數據源是通過網絡傳輸的,不能夠重複獲取。
接收數據根據數據源不同容錯級別不同
with file
:通過hdfs等文件系統中讀取數據時可以保證exactly-oncewith reciever-base-source
:reliable reciever
:當reciever接收失敗時不給數據源答覆接收成功,在reciever重啓後繼續接收unreliable reciever
:接收數據後不給數據源返回接收結果,則數據源也不會再次下發數據
sparkStreaming通過write-ahead-logs 提供了at least once的保證。在spark1.3版本之後,針對kafka數據源,可以做到exactly once ,更多內容
輸出容錯
類似於foreachRdd操作,可以保證at least once,如果輸出時想實現exactly once可通過以下兩種方式:
Idempotent updates
:冪等更新,多次嘗試將數據寫入同一個文件Transactional updates
:事物更新,實現方式:通過batch time和the index of rdd實現RDD的唯一標識,通過唯一標識去更新外部系統,即如果已經存在則跳過更新,如果不存在則更新。eg:
dstream.foreachRDD { (rdd, time) =>
rdd.foreachPartition { partitionIterator =>
val partitionId = TaskContext.get.partitionId()
val uniqueId = generateUniqueId(time.milliseconds, partitionId)
// use this uniqueId to transactionally commit the data in partitionIterator
}
}
調優
sparkStreaming調優主要從兩方面進行:開源節流——提高處理速度和減少輸入數據。
- 行時間優化
- 設置合理的批處理時間和窗口大小
- 提高並行度
- 增加接收器數目
- 將接收到數據重新分區
- 提高聚合計算的並行度,例如對reduceByKey等shuffle操作設置跟高的並行度
- 內存使用與垃圾回收
- 控制批處理時間間隔內的數據量
- 及時清理不再使用的數據
- 減少序列化和反序列化負擔
詳情參考:http://spark.apache.org/docs/latest/tuning.html#level-of-parallelism