簡介
Spark Streaming是核心Spark API的擴展,可對實時數據流進行可擴展,高吞吐量,容錯處理。實時流可以有許多數據來源(例如Kafka,Flume,Kinesis或TCP套接字)等,并可以使用高級功能(如map,reduce,join和window)組成的復雜算法來處理數據。經過處理后的數據可以寫入到文件系統、數據庫、實時儀表盤等。在Spark內部,Spark Streaming接收實時輸入流,并將其分成若干批,這些批次被送進Spark Engine中處理,最后按批次產生最后的結果。Spark Streaming的處理管道示意圖如下:
背景知識
什么是流處理
在正式介紹Spark Streaming之前,我們先來介紹一下什么叫做數據流。
流處理是連續處理新到來的數據以更新計算結果的行為。在流處理中,輸入數據是無邊界的,沒有預定的開始或結束。它是一系列到達流處理系統的事件(例如,信用卡交易,單擊網站動作,或從物聯網I o T傳感器讀取的數據),用戶應用程序對此事件流可以執行各種查詢操作(例如,跟蹤每種事件類型的發生次數,或將這些事件按照某時間窗口聚合)。應用程序在運行時將輸出多個版本的結果,或者在某外部系統 (如鍵值存儲)中持續保持最新的聚合結果。
當然,我們可以將流處理(stream processing)與批處理(batch processing)進行比較,批處理是在固定大小輸入數據集上進行計算的。通常,這可能是數據倉庫中的大規模數據集,其包含來自應用程序的所有歷史事件(例如,過去一個月的所有網站訪問記錄或傳感器記錄的 數據)。批處理也可以進行查詢計算,與流處理差不多,但只計算一次結果。
流處理的應用場景
流處理系統主要有以下6個應用場景:
- 通知和警報
- 實時報告
- 增量ETL(Extract-Transform-Load)
最常見的流處理應用程序之一是減少公司在信息檢索時必須忍受的延遲時間,簡而言之,“把批處理任務用流處理方式執行”。 - 實時數據更新來提供實時服務
- 實時決策
流處理系統的實時決策包括分析新的輸入,根據業務邏輯自動作出決策來應對新數據。 - 在線機器學習
結合用戶的實時流數據和歷史數據來進行在線實時推斷。
流處理的優點
在大多數情況下,批處理更容易理解、更容易調試、也更容易編寫應用程序。此外,批量處理數據也使得數據處理的吞吐量大大高于許多流處理系統。然而,流處理對于以下兩種情況非常必要。
-
流處理可以降低系統延遲時間
當你的應用程序需要快速響應時間(在分鐘、秒或毫秒級別上),你需要一個可以將狀態保存在內存中的流處理系統,以獲得更好的性能。 -
自動增量計算,高效更新結果
流處理系統可以記住以前計算的狀態,只計算新數據。而對于傳統批處理系統來說,必須加載所有時間段的數據才可以做到,并且還要添加額外的代碼來實現相應功能。
Spark Streaming
Spark Streaming是用來處理實時數據流數據的,它是Spark Core API的一個非常有用的擴展。Spark Streaming可以對實時數據流進行高吞吐量、包含容錯的流式處理。
Spark Streaming提供了一個高級抽象對象,名為discretized stream或者也叫DStream,用于代表連續的數據流。DStreams可以從輸入數據源比如Kafka, Flume, and Kinesis等中創建得到,也可以通過對已有的DStream進行高階操作得到。DStream代表的其實是一系列的RDDs。
Spark Streaming特性
Spark 流處理主要有以下6個特點:
- 可擴展性
Spark Streaming可以很容易地拓展到成千上百個節點上 - 速度
Spark Streaming實現了低延遲 - 容錯能力
Spark 能快讀地從錯誤中恢復 - 易集成到Spark體系
Spark集成了批處理和實時處理功能 - 商業分析
Spark Streaming可以用在商業數據分析中,用來追蹤用戶行為數據
Spark Streaming工作流程
Spark Streaming工作流程分為四個階段。第一個是從各種數據源中獲取流數據。這些數據源可以是流式數據源比如Akka, Kafka, Flume, AWS或者Parquet這樣的實時數據流;也包括HBase, MySQL, PostgreSQL, Elastic Search, Mongo DB 以及 Cassandra這些用于靜態或者批量流的數據源。一旦得到了數據流,Spakr可以在其之上使用Spark MLib API來進行機器學習算法處理,也可以使用Spark SQL來執行相關操作。最終,這些流的輸出可以被存儲在各種類型的數據存儲系統中,比如HBase, Cassandra, MemSQL, Kafka, Elastic Search, HDFS 以及本地文件系統。Spark Streaming 基礎組件
1. Streaming Context
Streaming Context用來消耗數據流。通過對它注冊一個輸入數據流,可以產生一個Receiver對象。這是使用Spark Streaming流處理功能的入口。Spark對包括Twitter、Akka和ZeroMQ等一些列數據源提供了默認的數據流讀取實現。一個StreamContext對象可以從SparkContext對象構造出來。SparkContext對象表示與Spark集群的連接關系,可以用來在該集群上創建RDD、累加器和廣播變量。
可以通過以下兩種方式來創建一個StreamingContext對象。
- 通過已有的SparkContext對象來創建:
import org.apache.spark._
import org.apache.spark.streaming._
var ssc = new StreamingContext(sc,Seconds(1)) //這個處理間隔時間要根據具體業務來設定
- 通過SparkCconf對象來創建:
import org.apache.spark._
import org.apache.spark.streaming._
val conf = new SparkConf().setAppName("SparkStreaming").setMaster("local[*]")
val ssc = new StreamingContext(conf, Seconds(1)) //這個處理間隔時間要根據具體業務來設定
在定義好StreamingContext對象之后,可以做以下事情:
- 通過創建輸入DStreams來定義輸入源
- 通過對DSteams應用Transformation和Action操作來定義流相關操作
- 使用streamingContext.start()來開啟數據接收以及處理
- 使用streamingContext.awaitTermination()來等待數據處理停止(主動停止或者錯誤發生)
- 使用StreamContext.stop()來手動停止數據處理
注意事項:
- 一旦一個StreamingContext被啟動了,就無法再繼續向它添加新的計算規則了
- 一旦一個StreamingContext被啟動了,它就無法再被重新啟動
- 同一個時間段,只能有一個StreamContext存在與JVM中
- 對StreamingContext執行stop操作,同樣也適用于SparkContext。如果只想停止StreamContext,那么請使用stopSparkContext
- 一個SparkContext可以被多次使用來創建多個StreamingContext,只要之前的StreamingContext已經被停止了。
2. DStream
Discretized Stream 或者 DStream 是Spark Streaming提供的基礎抽象對象。它代表了連續的數據流,可以是從數據源中獲取到的數據流,也可以是通過對輸入流進行Transformation操作之后得到的處理后的數據流。在Spark內部,DStream代表的是一個RDD序列。DStream中的每個RDD包含了數據流按照指定時間間隔分割后的某一段。如下圖所示:對DStream的Transformation操作與RDD類似,并且在普通RDD上的Transformation操作大多數在DStream上也可用。
這里再補充一些Transformation操作:
transform操作
Transform允許在DStream 上執行任意的 RDD-to-RDD 函數,即使這些函數并沒有在 DStream 的 API 中暴露出來,通過該函數可以方便的擴展 Spark API。該函數每一批次調度一次。其實也就是對 DStream 中的 RDD 應用轉換。join操作
兩個流之間的 join 需要兩個流的批次大小一致,這樣才能做到同時觸發計算。計算過程就是 對當前批次的兩個流中各自的 RDD 進行 join,與兩個 RDD的join效果相同。除此之外,也可以同樣對DStream使用leftOuterJoin, rightOuterJoin, fullOuterJoin等操作。-
UpdateStateByKey操作
UpdateStateByKey 原語用于記錄歷史記錄,有時,我們需要在 DStream中跨批次維護狀態(例 如流計算中累加 wordcount)。針對這種情況,updateStateByKey()為我們提供了對一個狀態變量的訪問,用于鍵值對形式的 DStream。給定一個由(鍵,事件)對構成的 DStream,并傳遞一個指定如何根據新的事件更新每個鍵對應狀態的函數,它可以構建出一個新的 DStream,其內部數據為(鍵,狀態) 對。UpdateStateByKey允許開發者保存任意信息,并且根據新的數據對信息進行持續更新。要想使用這個功能,分為以下兩步:- 定義狀態: 狀態可以是任意類型的
- 定義狀態更新函數: 定義一個函數,它來決定如何根據歷史數據和從輸入流中得到的新數據來對狀態進行更新
在每個批次中,Spark都會對所有的key應用狀態更新函數,不管這些key在一個批次中是否有數據到來。如果更新函數返回None,那么這個key-value將會被刪除。
注意:使用 updateStateByKey 需要對檢查點目錄進行配置,因為這個操作會使用檢查點來保存狀態。
-
Window操作
Spark Streaming也提供了windowed操作,它允許開發者在一個滑動窗口上應用Transformation操作。示意圖如下:
window操作- 窗口長度: 即窗口的持續時間,即窗口包含的時間單位,上圖中是3
- 滑動間隔時間: 即執行窗口操作的周期,即窗口每滑動多少個時間單位就執行一次窗口操作,上圖中是2
注意:
這兩個參數必須是源DStream批處理周期的整數倍。
關于Window操作還有以下方法:
(1)window(windowLength, slideInterval): 基于對源 DStream 窗化的批次進行計算返回一個新的 Dstream
(2)countByWindow(windowLength, slideInterval): 返回一個滑動窗口計數流中的元素個數
(3)reduceByWindow(func, windowLength, slideInterval): 通過使用自定義函數整合滑動區間流元素來創建一個新的單元素流
(4)reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]): 當在一個(K,V)對的 DStream 上調用此函數,會返回一個新(K,V)對 DStream,此處通過對滑動窗口中批次數據使用 reduce 函數來整合每個 key 的 value 值。
3. Input DStream
Input DStream是代表從原始數據流源頭得到的數據流。每個Input DStream都有一個Receiver對象與之搭配,Receiver對象負責從源頭獲取數據流然后保存在Spark內存中用于處理。Spark Streaming的內置數據源有兩種分類,如下:
- 基礎源:這些數據源直接在SparkContext中可用,比如內置的文件系統和Socket連接
- 高級數據源:比如像 Kafka, Flume, Kinesis等,這些數據源需要通過單獨的工具程序類獲取
4. Output DStream
輸出操作允許將DStream的數據寫入到外部的系統中,比如數據庫,文件系統。與RDD中的惰性求值類似,OutPut操作執行的時候,才會觸發所有應用在DStream上的Transformation操作的實際執行。輸出操作一般有:
- print()
在運行流程序的驅動結點上打印DStream中每一批次數據的最開始10個元素。這用于開發和調試. - saveAsTextFiles(prefix, [suffix])
以 text 文件形式存儲這個 DStream 的內容。每一批次的存儲文件名基于參數中的 prefix 和 suffix。 - saveAsObjectFiles(prefix, [suffix])
以 Java 對象序列化的方式將 Stream 中的數據保存為SequenceFiles . 每一批次的存儲文件名基于參數中的為"prefix-TIME_IN_MS[.suffix]". - saveAsHadoopFiles(prefix, [suffix])
將 Stream 中的數據保存為 Hadoop files. 每一批次的存儲文件名基于參數中的為"prefix-TIME_IN_MS[.suffix]" - foreachRDD(func)
這是最通用的輸出操作,即將函數 func 用于產生于 stream 的每一個RDD。其中參數傳入的函數 func 應該實現將每一個 RDD 中數據推送到外部系統,如將RDD 存入文件或者通過網絡將其寫入數據庫。
5. Caching
DStream可以允許開發者在內存中緩存或者持久化流數據,如果DStream中的數據會被多次計算的話,這通常是很有用的一種做法。可以通過調用DStream的persist()方法來實現緩存操作。對于從網絡中接收到的數據流,比如Kafka,Flume,Socket等,默認的數據持久化級別是將數據復制兩份,然后存儲到兩個節點中以實現容錯機制。
6. CheckPoint
一個流程序必須24/7全天候運行,因此必須能夠抵抗與應用程序邏輯無關的故障,比如系統錯誤、JVM崩潰等。為了實現這種功能,Spark Streaming需要保存足夠的信息到容錯存儲系統中,以便能夠從故障中恢復。CheckPoint有兩種類型:
- Metadata checkpointing
- Data checkpointing
簡單描述一下,Metadata checkpointing 主要是用于從驅動錯誤等中恢復;對于某些有狀態的Transformation操作,如果期間出現了錯誤,可以使用Data checkpointing從錯誤中恢復。
如果應用程序有以下要求,那么必須使用Checkpointing技術:
- 使用了有狀態的Transformation操作
比如使用了updateStateByKey或者reduceByKeyAndWindow等操作,那么就必須提供checkpoint的存儲路徑,以便允許定期執行RDD checkpointing操作,從而保存相應的信息。 - 需要從應用程序的驅動錯誤中恢復
對于普通的流式程序,即沒有執行帶狀態的Transformation操作,那么無需打開checkpointing操作。
通過在容錯的、可靠的文件系統上設置一個檢查點目錄即可啟動Spark的CheckPointing功能,即可以保存檢查點的信息,以便令程序可以從錯誤中恢復。可以通過執行streamingContext.checkpoint(checkpointDirectory)來實現這個功能。如果應用程序要使用帶狀態的Transformation操作,這一步是必須的。創建Checkpointing的示意代碼如下:
// Function to create and setup a new StreamingContext
def functionToCreateContext(): StreamingContext = {
val ssc = new StreamingContext(...) // new context
val lines = ssc.socketTextStream(...) // create DStreams
...
ssc.checkpoint(checkpointDirectory) // set checkpoint directory
ssc
}
// Get StreamingContext from checkpoint data or create a new one
val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _)
// Do additional setup on context that needs to be done,
// irrespective of whether it is being started or restarted
context. ...
// Start the context
context.start()
context.awaitTermination()
如果checkpointDirectory目錄存在,那么context會從checkpoint數據中重建;如果目錄不存在,那么函數functionToCreateContext會被調用以創建一個新的context對象,并且設置DStream。