Spark Streaming高級

一、緩存或持久化

和RDD相似,DStreams也允許開發者持久化流數據到內存中。在DStream上使用 persist() 方法可以自動地持久化DStream中的RDD到內存中。如果DStream中的數據需要計算多次,這是非常有用的。像reduceByWindow 和 reduceByKeyAndWindow 這種窗口操作、 updateStateByKey 這種基於狀態的操作,持久化是默認的,不需要開發者調用 persist() 方法。
例如通過網絡(如kafka,flume等)獲取的輸入數據流,默認的持久化策略是複製數據到兩個不同的節點以容錯。
注意,與RDD不同的是,DStreams默認持久化級別是存儲序列化數據到內存中。

二、Checkpointing

一個流應用程序必須全天候運行,所有必須能夠解決應用程序邏輯無關的故障(如系統錯誤,JVM崩潰等)。爲了使這成爲可能,Spark Streaming需要checkpoint足夠的信息到容錯存儲系統中,以使系統從故障中恢復。
Metadata checkpointing:保存流計算的定義信息到容錯存儲系統如HDFS中。這用來恢復應用程序中運行worker的節點的故障。元數據包括
1.Configuration :創建Spark Streaming應用程序的配置信息
2.DStream operations :定義Streaming應用程序的操作集合
3.Incomplete batches:操作存在隊列中的未完成的批
4.Data checkpointing :保存生成的RDD到可靠的存儲系統中,這在有狀態transformation(如結合跨多個批次的數據)中是必須的。在這樣一個transformation中,生成的RDD依賴於之前批的RDD,隨着時間的推移,這個依賴鏈的長度會持續增長。在恢復的過程中,爲了避免這種無限增長。有狀態的transformation的中間RDD將會定時地存儲到可靠存儲系統中,以截斷這個依鏈。
元數據checkpoint主要是爲了從driver故障中恢復數據。如果transformation操作被用到了,數據checkpoint即使在簡單的操作中都是必須的。

何時checkpoint:
    應用程序在下面兩種情況下必須開啓checkpoint:
        1.使用有狀態的transformation。如果在應用程序中用到了 updateStateByKey 或者reduceByKeyAndWindow ,checkpoint目錄必需提供用以定期checkpoint RDD。
        2.從運行應用程序的driver的故障中恢復過來。使用元數據checkpoint恢復處理信息。
    注意,沒有前述的有狀態的transformation的簡單流應用程序在運行時可以不開啓checkpoint。在這種情況下,從driver故障的恢復將是部分恢復(接收到了但是還沒有處理的數據將會丟失)。這通常是可以接受的,許多運行的Spark Streaming應用程序都是這種方式。
怎樣配置Checkpointing:
    在容錯、可靠的文件系統(HDFS、s3等)中設置一個目錄用於保存checkpoint信息。這可以通過streamingContext.checkpoint(checkpointDirectory) 方法來做。這運行你用之前介紹的有狀態transformation。另外,如果你想從driver故障中恢復,你應該以下面的方式重寫你的Streaming應用程序。
        1.當應用程序是第一次啓動,新建一個StreamingContext,啓動所有Stream,然後調用 start() 方法
        2.當應用程序因爲故障重新啓動,它將會從checkpoint目錄checkpoint數據重新創建StreamingContext

    // 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,

    context. ...
    // Start the context

    context.start()
    context.awaitTermination()
    如果 checkpointDirectory 存在,上下文將會利用checkpoint數據重新創建。如果這個目錄不存在,將會調用 functionToCreateContext 函數創建一個新的上下文,建立DStreams。

    除了使用 getOrCreate ,開發者必須保證在故障發生時,driver處理自動重啓。只能通過部署運行應用程序的基礎設施來達到該目的。

    注意,RDD的checkpointing有存儲成本。這會導致批數據(包含的RDD被checkpoint)的處理時間增加。因此,需要小心的設置批處理的時間間隔。在最小的批容量(包含1秒的數據)情況下,checkpoint每批數據會顯著的減少操作的吞吐量。相反,checkpointing太少會導致譜系以及任務大小增大,這會產生有害的影響。因爲有狀態的transformation需要RDD checkpoint。默認的間隔時間是批間隔時間的倍數,最少10秒。它可以通過 dstream.checkpoint 來設置。典型的情況下,設置checkpoint間隔是DStream的滑動間隔的5-10大小是一個好的嘗試。

三、部署應用程序和升級應用程序

**部署應用程序**:
    運行一個Spark Streaming應用程序,有下面一些步驟:
        1.有管理器的集羣-這是任何Spark應用程序都需要的需求,詳見部署指南
        2.將應用程序打爲jar包-你必須編譯你的應用程序爲jar包。如果你用spark-submit啓動應用程序,你不需要將Spark和Spark Streaming打包進這個jar包。如果你的應用程序用到了高級源(如kafka,flume),你需要將它們關聯的外部artifact以及它們的依賴打包進需要部署的應用程序jar包中。例如,一個應用程序用到了 TwitterUtils ,那麼就需要將 spark-streaming-twitter_2.10 以及它的所有依賴打包到應用程序jar中。
        3.爲executors配置足夠的內存-因爲接收的數據必須存儲在內存中,executors必須配置足夠的內存用來保存接收的數據。注意,如果你正在做10分鐘的窗口操作,系統的內存要至少能保存10分鐘的數據。所以,應用程序的內存需求依賴於使用它的操作。
        4.配置checkpointing-如果stream應用程序需要checkpointing,然後一個與Hadoop API兼容的容錯存儲目錄必須配置爲檢查點的目錄,流應用程序將checkpoint信息寫入該目錄用於錯誤恢復。
        5.配置應用程序driver的自動重啓-爲了自動從driver故障中恢復,運行流應用程序的部署設施必須能監控driver進程,如果失敗了能夠重啓它。不同的集羣管理器,有不同的工具得到該功能
        6.Spark Standalone:一個Spark應用程序driver可以提交到Spark獨立集羣運行,也就是說driver運行在一個worker節點上。進一步來看,獨立的集羣管理器能夠被指示用來監控driver,並且在driver失敗(或者是由於非零的退出代碼如exit(1),或者由於運行driver的節點的故障)的情況下重啓driver。
        7.YARN:YARN爲自動重啓應用程序提供了類似的機制。
        8.Mesos: Mesos可以用Marathon提供該功能
        9.配置write ahead logs-在Spark 1.2中,爲了獲得極強的容錯保證,我們引入了一個新的實驗性的特性-預寫日誌(write ahead logs)。如果該特性開啓,從receiver獲取的所有數據會將預寫日誌寫入配置的checkpoint目錄。這可以防止driver故障丟失數據,從而保證零數據丟失。這個功能可以通過設置配置參數 spark.streaming.receiver.writeAheadLogs.enable 爲true來開啓。然而,這些較強的語義可能以receiver的接收吞吐量爲代價。這可以通過並行運行多個receiver增加吞吐量來解決。另外,當預寫日誌開啓時,Spark中的複製數據的功能推薦不用,因爲該日誌已經存儲在了一個副本在存儲系統中。可以通過設置輸入DStream的存儲級別爲 StorageLevel.MEMORY_AND_DISK_SER 獲得該功能。

**升級應用程序代碼**:
    如果運行的Spark Streaming應用程序需要升級,有兩種可能的方法:
        1.啓動升級的應用程序,使其與未升級的應用程序並行運行。一旦新的程序(與就程序接收相同的數據)已經準備就緒,舊的應用程序就可以關閉。這種方法支持將數據發送到兩個不同的目的地(新程序一個,舊程序一個)
        2.首先,平滑的關閉( StreamingContext.stop(...) 或JavaStreamingContext.stop(...) )現有的應用程序。在關閉之前,要保證已經接收的數據完全處理完。然後,就可以啓動升級的應用程序,升級的應用程序會接着舊應用程序的點開始處理。這種方法僅支持具有源端緩存功能的輸入源(如flume,kafka),這是因爲當舊的應用程序已經關閉,升級的應用程序還沒有啓動的時候,數據需要被緩存。

四、監控應用程序

除了Spark的監控功能,Spark Streaming增加了一些專有的功能。應用StreamingContext的時候,Spark web UI顯示添加的 Streaming 菜單,用以顯示運行的receivers(receivers是否是存活狀態、接收的記錄數、receiver錯誤等)和完成的批的統計信息(批處理時間、隊列等待等待)。這可以用來監控流應用程序的處理過程。

在WEB UI中的 Processing Time 和 Scheduling Delay 兩個度量指標是非常重要的。第一個指標表示批數據處理的時間,第二個指標表示前面的批處理完畢之後,當前批在隊列中的等待時間。如果批處理時間比批間隔時間持續更長或者隊列等待時間持續增加,這就預示系統無法以批數據產生的速度處理這些數據,整個處理過程滯後了。在這種情況下,考慮減少批處理時間。

Spark Streaming程序的處理過程也可以通過StreamingListener接口來監控,這個接口允許你獲得receiver狀態和處理時間。注意,這個接口是開發者API,它有可能在未來提供更多的信息。

五、性能調優

集羣中的Spark Streaming應用程序獲得最好的性能需要一些調整。這章將介紹幾個參數和配置,提高Spark Streaming應用程序的性能。你需要考慮兩件事情:
1.高效地利用集羣資源減少批數據的處理時間
2.設置正確的批容量(size),使數據的處理速度能夠趕上數據的接收速度
    減少批數據的執行時間
    設置正確的批容量
    內存調優

六、減少批數據的執行時間

數據接收的並行水平:
    通過網絡(如kafka,flume,socket等)接收數據需要這些數據反序列化並被保存到Spark中。如果數據接收成爲系統的瓶頸,就要考慮並行地接收數據。注意,每個輸入DStream創建一個 receiver (運行在worker機器上)接收單個數據流。創建多個輸入DStream並配置它們可以從源中接收不同分區的數據流,從而實現多數據流接收。例如,接收兩個topic數據的單個輸入DStream可以被切分爲兩個kafka輸入流,每個接收一個topic。這將在兩個worker上運行兩個 receiver ,因此允許數據並行接收,提高整體的吞吐量。多個DStream可以被合併生成單個DStream,這樣運用在單個輸入DStream的transformation操作以運用在合併的DStream上。

    val numStreams = 5
    val kafkaStreams = (1 to numStreams).map { i => KafkaUtils.createStream(...) }
    val unifiedStream = streamingContext.union(kafkaStreams)
    unifiedStream.print()

    另外一個需要考慮的參數是 receiver 的阻塞時間。對於大部分的 receiver ,在存入Spark內存之前,接收的數據都被合併成了一個大數據塊。每批數據中塊的個數決定了任務的個數。這任務是用類似map的transformation操作接收的數據。阻塞間隔由配置參數 spark.streaming.blockInterval 決定,默認的值是200毫秒。

    多輸入流或者多 receiver 的可選的方法是明確地重新分配輸入數據流(利用inputStream.repartition(<number of partitions>) ),在進一步操作之前,通過集羣的機器數分配接收的批數據。

數據處理的並行水平:
    如果運行在計算stage上的併發任務數不足夠大,就不會充分利用集羣的資源。例如,對於分佈式reduce操作如 reduceByKey 和 reduceByKeyAndWindow ,默認的併發任務數通過配置屬性來確定(configuration.html#spark-properties) spark.default.parallelism 。你可以通過參數(PairDStreamFunctions(api/scala/index.html#org.apache.spark.streaming.dstream.PairDStreamFunctions))傳遞並行度,或者設置參數 spark.default.parallelism 修改默認值。

數據序列化:
    數據序列化的總開銷是平常大的,特別是當sub-second級的批數據被接收時。下面有兩個相關點:
        Spark中RDD數據的序列化。關於數據序列化請參照Spark優化指南。注意,與Spark不同的是,默認的RDD會被持久化爲序列化的字節數組,以減少與垃圾回收相關的暫停。
        輸入數據的序列化。從外部獲取數據存到Spark中,獲取的byte數據需要從byte反序列化,然後再按照Spark的序列化格式重新序列化到Spark中。因此,輸入數據的反序列化花費可能是一個瓶頸。

任務的啓動開支:
    每秒鐘啓動的任務數是非常大的(50或者更多)。發送任務到slave的花費明顯,這使請求很難獲得亞秒(sub-second)級別的反應。通過下面的改變可以減小開支
    1.任務序列化。運行kyro序列化任何可以減小任務的大小,從而減小任務發送到slave的時間。
    2.執行模式。在Standalone模式下或者粗粒度的Mesos模式下運行Spark可以在比細粒度Mesos模式下運行Spark獲得更短的任務啓動時間。可以在在Mesos下運行Spark中獲取更多信息。

七、設置正確的批容量

    爲了Spark Streaming應用程序能夠在集羣中穩定運行,系統應該能夠以足夠的速度處理接收的數據(即處理速度應該大於或等於接收數據的速度)。這可以通過流的網絡UI觀察得到。批處理時間應該小於批間隔時間。

    根據流計算的性質,批間隔時間可能顯著的影響數據處理速率,這個速率可以通過應用程序維持。可以考慮 WordCountNetwork 這個例子,對於一個特定的數據處理速率,系統可能可以每2秒打印一次單詞計數(批間隔時間爲2秒),但無法每500毫秒打印一次單詞計數。所以,爲了在生產環境中維持期望的數據Spark 處理速率,就應該設置合適的批間隔時間(即批數據的容量)。

    找出正確的批容量的一個好的辦法是用一個保守的批間隔時間(5-10,秒)和低數據速率來測試你的應用程序。爲了驗證你的系統是否能滿足數據處理速率,你可以通過檢查端到端的延遲值來判斷(可以在Spark驅動程序的log4j日誌中查看"Total delay"或者利用StreamingListener接口)。如果延遲維持穩定,那麼系統是穩定的。如果延遲持續增長,那麼系統無法跟上數據處理速率,是不穩定的。你能夠嘗試着增加數據處理速率或者減少批容量來作進一步的測試。注意,因爲瞬間的數據處理速度增加導致延遲瞬間的增長可能是正常的,只要延遲能重新回到了低值(小於批容量)。

八、內存調優

調整內存的使用以及Spark應用程序的垃圾回收行爲已經在Spark優化指南中詳細介紹。在這一節,我們重點介紹幾個強烈推薦的自定義選項,它們可以減少Spark Streaming應用程序垃圾回收的相關暫停,獲得更穩定的批處理時間。
1.Default persistence level of DStreams:和RDDs不同的是,默認的持久化級別是序列化數據到內存中(DStream是 StorageLevel.MEMORY_ONLY_SER ,RDD是 StorageLevel.MEMORY_ONLY )。即使保存數據爲序列化形態會增加序列化/反序列化的開銷,但是可以明顯的減少垃圾回收的暫停。

2.Clearing persistent RDDs:默認情況下,通過Spark內置策略(LUR),Spark Streaming生成的持久化RDD將會從內存中清理掉。如果spark.cleaner.ttl已經設置了,比這個時間存在更老的持久化RDD將會被定時的清理掉。正如前面提到的那樣,這個值需要根據Spark Streaming應用程序的操作小心設置。然而,可以設置配置選項 spark.streaming.unpersist 爲true來更智能的去持久化(unpersist)RDD。這個配置使系統找出那些不需要經常保有的RDD,然後去持久化它們。這可以減少Spark RDD的內存使用,也可能改善垃圾回收的行爲。

3.Concurrent garbage collector:使用併發的標記-清除垃圾回收可以進一步減少垃圾回收的暫停時間。儘管併發的垃圾回收會減少系統的整體吞吐量,但是仍然推薦使用它以獲得更穩定的批處理時間。

九、容錯語義

RDD的基本容錯語義:
    1.一個RDD是不可變的、確定可重複計算的、分佈式數據集。每個RDD記住一個確定性操作的譜系(lineage),這個譜系用在容錯的輸入數據集上來創建該RDD。

    2.如果任何一個RDD的分區因爲節點故障而丟失,這個分區可以通過操作譜系從源容錯的數據集中重新計算得到。

    3.假定所有的RDD transformations是確定的,那麼最終轉換的數據是一樣的,不論Spark機器中發生何種錯誤。

    4.Data received and replicated :在單個worker節點的故障中,這個數據會倖存下來,因爲有另外一個節點保存有這個數據的副本。

    5.Data received but buffered for replication:因爲沒有重複保存,所以爲了恢復數據,唯一的辦法是從源中重新讀取數據。

重點關心的兩種:

    1.worker節點故障:任何運行executor的worker節點都有可能出故障,那樣在這個節點中的所有內存數據都會丟失。如果有任何receiver運行在錯誤節點,它們的緩存數據將會丟失
    2.Driver節點故障:如果運行Spark Streaming應用程序的Driver節點出現故障,很明顯SparkContext將會丟失,所有執行在其上的executors也會丟失。

作爲輸入源的文件語義:
    如果所有的輸入數據都存在於一個容錯的文件系統如HDFS,Spark Streaming總可以從任何錯誤中恢復並且執行所有數據。這給出了一個恰好一次(exactly-once)語義,即無論發生什麼故障,所有的數據都將會恰好處理一次。

基於receiver的輸入源語義:
    對於基於receiver的輸入源,容錯的語義既依賴於故障的情形也依賴於receiver的類型。正如之前討論的,有兩種類型的receiver:
        1.Reliable Receiver:這些receivers只有在確保數據複製之後纔會告知可靠源。如果這樣一個receiver失敗了,緩衝(非複製)數據不會被源所承認。如果receiver重啓,源會重發數據,因此不會丟失數據。
        2.Unreliable Receiver:當worker或者driver節點故障,這種receiver會丟失數據

輸出操作的語義:
    根據其確定操作的譜系,所有數據都被建模成了RDD,所有的重新計算都會產生同樣的結果。所有的DStream transformation都有exactly-once語義。那就是說,即使某個worker節點出現故障,最終的轉換結果都是一樣。然而,輸出操作(如 foreachRDD )具有 at-least once 語義,那就是說,在有worker事件故障的情況下,變換後的數據可能被寫入到一個外部實體不止一次。利用 saveAs***Files 將數據保存到HDFS中的情況下,以上寫多次是能夠被接受的(因爲文件會被相同的數據覆蓋)。
發佈了58 篇原創文章 · 獲贊 40 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章