目錄
一.Spark Streaming基礎
????1.Spark Streaming簡介
????2.Spark Streaming的特點
????3.Spark Streaming的內部結構
二.Spark Streaming進階
????1.StreamingContext對象詳解
????2.離散流(DStreams):Discretized Streams
????3.DStream中的轉換操作(transformation)
????4.窗口操作
????5.輸入DStreams和接收器
????6.DStreams的輸出操作
????7.DataFrame和SQL操作
????8.緩存/持久化
????9.檢查點支持
三.高級數據源
????1.Spark Streaming接收Flume數據
????2.Spark Streaming接收Kafka數據
四.性能優化
????1.減少批數據的執行時間
????2.設置正確的批容量
????3.內存調優
一.Spark Streaming基礎
1.Spark Streaming簡介
????Spark Streaming是核心Spark API的擴展,可實現可擴展、高吞吐量、可容錯的實時數據流處理。數據可以從諸如Kafka,Flume,Kinesis或TCP套接字等眾多來源獲取,并且可以使用由高級函數(如map,reduce,join和window)開發的復雜算法進行流數據處理。最后,處理后的數據可以被推送到文件系統,數據庫和實時儀表板。而且,您還可以在數據流上應用Spark提供的機器學習和圖處理算法。
2.Spark Streaming的特點
(1)易用:集成在Spark中
(2)容錯性:底層RDD,RDD本身就具備容錯機制。
(3)支持多種編程語言:Java Scala Python
3.Spark Streaming的內部結構
????在內部,它的工作原理如下。Spark Streaming接收實時輸入數據流,并將數據切分成批,然后由Spark引擎對其進行處理,最后生成“批”形式的結果流。
????Spark Streaming將連續的數據流抽象為discretizedstream或DStream。在內部,DStream 由一個RDD序列表示。
二.Spark Streaming進階
1.StreamingContext對象詳解
(1)初始化StreamingContext
(a)方式一:從SparkConf對象中創建
val conf = new SparkConf().setAppName("MyNetworkWordCount").setMaster("local[2]")
val src = new StreamContext(conf, Second(5))
(b)方式二:從一個現有的SparkContext實例中創建
scala >import org.apache.spark.streaming.{Second,StreamingContext}
scala >val ssc=new StreamContext(sc,Second(1))
(2).程序中的幾點說明:
appName參數是應用程序在集群UI上顯示的名稱。
master是Spark,Mesos或YARN集群的URL,或者一個特殊的“local [*]”字符串來讓程序以本地模式運行。
當在集群上運行程序時,不需要在程序中硬編碼master參數,而是使用spark-submit提交應用程序并將master的URL以腳本參數的形式傳入。但是,對于本地測試和單元測試,您可以通過“local[]”來運行Spark Streaming程序(請確保本地系統中的cpu核心數夠用)
StreamingContext會內在的創建一個SparkContext的實例(所有Spark功能的起始點),你可以通過ssc.sparkContext訪問到這個實例。
批處理的時間窗口長度必須根據應用程序的延遲要求和可用的集群資源進行設置。
(3).請務必記住以下幾點:
一旦一個StreamingContextt開始運作,就不能設置或添加新的流計算。
一旦一個上下文被停止,它將無法重新啟動。
同一時刻,一個JVM中只能有一個StreamingContext處于活動狀態。
StreamingContext上的stop()方法也會停止SparkContext。 要僅停止StreamingContext(保持SparkContext活躍),請將stop() 方法的可選參數stopSparkContext設置為false。
只要前一個StreamingContext在下一個StreamingContext被創建之前停止(不停止SparkContext),SparkContext就可以被重用來創建多個StreamingContext。
2.離散流(DStreams):Discretized Streams
(1)DiscretizedStream或DStream 是Spark Streaming對流式數據的基本抽象。它表示連續的數據流,這些連續的數據流可以是從數據源接收的輸入數據流,也可以是通過對輸入數據流執行轉換操作而生成的經處理的數據流。在內部,DStream由一系列連續的RDD表示,如下圖:
(2)舉例分析:在之前的NetworkWordCount的例子中,我們將一行行文本組成的流轉換為單詞流,具體做法為:將flatMap操作應用于名為lines的 DStream中的每個RDD上,以生成words DStream的RDD。如下圖所示:
但是DStream和RDD也有區別,下面畫圖說明:
3.DStream中的轉換操作(transformation)
最后兩個transformation算子需要重點介紹一下:
(1)transform(func)
(a)通過RDD-to-RDD函數作用于源DStream中的各個RDD,可以是任意的RDD操作,從而返回一個新的RDD
(b)舉例:在NetworkWordCount中,也可以使用transform來生成元組對
(2)updateStateByKey(func)
(a)操作允許不斷用新信息更新它的同時保持任意狀態。
- 定義狀態-狀態可以是任何的數據類型
- 定義狀態更新函數-怎樣利用更新前的狀態和從輸入流里面獲取的新值更新狀態
(b)重寫NetworkWordCount程序,累計每個單詞出現的頻率(注意:累計)
(c)輸出結果:
(3)注意:如果在IDEA中,不想輸出log4j的日志信息,可以將log4j.properties文件(放在src的目錄下)的第一行改為:
log4j.rootCategory=ERROR,console
4.窗口操作
????Spark Streaming還提供了窗口計算功能,允許您在數據的滑動窗口上應用轉換操作。下圖說明了滑動窗口的工作方式:
????如圖所示,每當窗口滑過originalDStream時,落在窗口內的源RDD被組合并被執行操作以產生windowed DStream的RDD。在上面的例子中,操作應用于最近3個時間單位的數據,并以2個時間單位滑動。這表明任何窗口操作都需要指定兩個參數。
- 窗口長度(windowlength) - 窗口的時間長度(上圖的示例中為:3)。
- 滑動間隔(slidinginterval) - 兩次相鄰的窗口操作的間隔(即每次滑動的時間長度)(上圖示例中為:2)。
????這兩個參數必須是源DStream的批間隔的倍數(上圖示例中為:1)。
????我們以一個例子來說明窗口操作。 假設您希望對之前的單詞計數的示例進行擴展,每10秒鐘對過去30秒的數據進行wordcount。為此,我們必須在最近30秒的pairs DStream數據中對(word, 1)鍵值對應用reduceByKey操作。這是通過使用reduceByKeyAndWindow操作完成的。
????一些常見的窗口操作如下表所示。所有這些操作都用到了上述兩個參數 - windowLength和slideInterval。
(1)window(windowLength, slideInterval)
- 基于源DStream產生的窗口化的批數據計算一個新的DStream
(2)countByWindow(windowLength, slideInterval)
- 返回流中元素的一個滑動窗口數
(3)reduceByWindow(func, windowLength, slideInterval)
- 返回一個單元素流。利用函數func聚集滑動時間間隔的流的元素創建這個單元素流。函數必須是相關聯的以使計算能夠正確的并行計算。
(4)reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks])
- 應用到一個(K,V)對組成的DStream上,返回一個由(K,V)對組成的新的DStream。每一個key的值均由給定的reduce函數聚集起來。注意:在默認情況下,這個算子利用了Spark默認的并發任務數去分組。你可以用numTasks參數設置不同的任務數
(5)reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks])
- 上述reduceByKeyAndWindow() 的更高效的版本,其中使用前一窗口的reduce計算結果遞增地計算每個窗口的reduce值。這是通過對進入滑動窗口的新數據進行reduce操作,以及“逆減(inverse reducing)”離開窗口的舊數據來完成的。一個例子是當窗口滑動時對鍵對應的值進行“一加一減”操作。但是,它僅適用于“可逆減函數(invertible reduce functions)”,即具有相應“反減”功能的減函數(作為參數invFunc)。 像reduceByKeyAndWindow一樣,通過可選參數可以配置reduce任務的數量。 請注意,使用此操作必須啟用檢查點。
(6)countByValueAndWindow(windowLength, slideInterval, [numTasks])
- 應用到一個(K,V)對組成的DStream上,返回一個由(K,V)對組成的新的DStream。每個key的值都是它們在滑動窗口中出現的頻率。
5.輸入DStreams和接收器
????輸入DStreams表示從數據源獲取輸入數據流的DStreams。在NetworkWordCount例子中,lines表示輸入DStream,它代表從netcat服務器獲取的數據流。每一個輸入流DStream和一個Receiver對象相關聯,這個Receiver從源中獲取數據,并將數據存入內存中用于處理。
????輸入DStreams表示從數據源獲取的原始數據流。Spark Streaming擁有兩類數據源:
- 基本源(Basic sources):這些源在StreamingContext API中直接可用。例如文件系統、套接字連接、Akka的actor等
- 高級源(Advanced sources):這些源包括Kafka,Flume,Kinesis,Twitter等等。
下面通過具體的案例,詳細說明:
(1)文件流:通過監控文件系統的變化,若有新文件添加,則將它讀入并作為數據流
需要注意的是:
- 這些文件具有相同的格式
- 這些文件通過原子移動或重命名文件的方式在dataDirectory創建
- 如果在文件中追加內容,這些追加的新數據也不會被讀取。
注意:要演示成功,需要在原文件中編輯,然后拷貝一份。
(2)RDD隊列流
????使用streamingContext.queueStream(queueOfRDD)創建基于RDD隊列的DStream,用于調試Spark Streaming應用程序。
(3)套接字流:通過監聽Socket端口來接收數據
6.DStreams的輸出操作
????輸出操作允許DStream的操作推到如數據庫、文件系統等外部系統中。因為輸出操作實際上是允許外部系統消費轉換后的數據,它們觸發的實際操作是DStream轉換。目前,定義了下面幾種輸出操作:
(1)foreachRDD的設計模式
DStream.foreachRDD是一個強大的原語,發送數據到外部系統中。
(a)第一步:創建連接,將數據寫入外部數據庫(使用之前的NetworkWordCount,改寫之前輸出結果的部分,如下)
出現以下Exception:
????原因是:Connection對象不是一個可被序列化的對象,不能RDD的每個Worker上運行;即:Connection不能在RDD分布式環境中的每個分區上運行,因為不同的分區可能運行在不同的Worker上。所以需要在每個RDD分區上單獨創建Connection對象。
(b)第二步:在每個RDD分區上單獨創建Connection對象,如下:
7.DataFrame和SQL操作
????我們可以很方便地使用DataFrames和SQL操作來處理流數據。您必須使用當前的StreamingContext對應的SparkContext創建一個SparkSession。此外,必須這樣做的另一個原因是使得應用可以在driver程序故障時得以重新啟動,這是通過創建一個可以延遲實例化的單例SparkSession來實現的。
????在下面的示例中,我們使用DataFrames和SQL來修改之前的wordcount示例并對單詞進行計數。我們將每個RDD轉換為DataFrame,并注冊為臨時表,然后在這張表上執行SQL查詢。
8.緩存/持久化
????與RDD類似,DStreams還允許開發人員將流數據保留在內存中。也就是說,在DStream上調用persist() 方法會自動將該DStream的每個RDD保留在內存中。如果DStream中的數據將被多次計算(例如,相同數據上執行多個操作),這個操作就會很有用。對于基于窗口的操作,如reduceByWindow和reduceByKeyAndWindow以及基于狀態的操作,如updateStateByKey,數據會默認進行持久化。 因此,基于窗口的操作生成的DStream會自動保存在內存中,而不需要開發人員調用persist()。
????對于通過網絡接收數據(例如Kafka,Flume,sockets等)的輸入流,默認持久化級別被設置為將數據復制到兩個節點進行容錯。
????請注意,與RDD不同,DStreams的默認持久化級別將數據序列化保存在內存中。
9.檢查點支持
????流數據處理程序通常都是全天候運行,因此必須對應用中邏輯無關的故障(例如,系統故障,JVM崩潰等)具有彈性。為了實現這一特性,Spark Streaming需要checkpoint足夠的信息到容錯存儲系統,以便可以從故障中恢復。
(1)一般會對兩種類型的數據使用檢查點:
(a)元數據檢查點(Metadatacheckpointing) - 將定義流計算的信息保存到容錯存儲中(如HDFS)。這用于從運行streaming程序的driver程序的節點的故障中恢復。元數據包括以下幾種:
- 配置(Configuration) - 用于創建streaming應用程序的配置信息。
- DStream操作(DStream operations) - 定義streaming應用程序的DStream操作集合。
- 不完整的batch(Incomplete batches) - jobs還在隊列中但尚未完成的batch。
(b)數據檢查點(Datacheckpointing) - 將生成的RDD保存到可靠的存儲層。對于一些需要將多個批次之間的數據進行組合的stateful變換操作,設置數據檢查點是必需的。在這些轉換操作中,當前生成的RDD依賴于先前批次的RDD,這導致依賴鏈的長度隨時間而不斷增加,由此也會導致基于血統機制的恢復時間無限增加。為了避免這種情況,stateful轉換的中間RDD將定期設置檢查點并保存到到可靠的存儲層(例如HDFS)以切斷依賴關系鏈。
????總而言之,元數據檢查點主要用于從driver程序故障中恢復,而數據或RDD檢查點在任何使用stateful轉換時是必須要有的。
(2)何時啟用檢查點:
對于具有以下任一要求的應用程序,必須啟用檢查點:
(a)使用狀態轉:如果在應用程序中使用updateStateByKey或reduceByKeyAndWindow(具有逆函數),則必須提供檢查點目錄以允許定期保存RDD檢查點。
(b)從運行應用程序的driver程序的故障中恢復:元數據檢查點用于使用進度信息進行恢復。
(3)如何配置檢查點:
????可以通過在一些可容錯、高可靠的文件系統(例如,HDFS,S3等)中設置保存檢查點信息的目錄來啟用檢查點。這是通過使用streamingContext.checkpoint(checkpointDirectory)完成的。設置檢查點后,您就可以使用上述的有狀態轉換操作。此外,如果要使應用程序從驅動程序故障中恢復,您應該重寫streaming應用程序以使程序具有以下行為:
(a)當程序第一次啟動時,它將創建一個新的StreamingContext,設置好所有流數據源,然后調用start()方法。
(b)當程序在失敗后重新啟動時,它將從checkpoint目錄中的檢查點數據重新創建一個StreamingContext。
使用StreamingContext.getOrCreate可以簡化此行為
(4)改寫之前的WordCount程序,使得每次計算的結果和狀態都保存到檢查點目錄下
通過查看HDFS中的信息,可以看到相關的檢查點信息,如下:
三.高級數據源
1.Spark Streaming接收Flume數據
(1)基于Flume的Push模式
????Flume被用于在Flume agents之間推送數據.在這種方式下,Spark Streaming可以很方便的建立一個receiver,起到一個Avro agent的作用.Flume可以將數據推送到改receiver.
(a)第一步:Flume的配置文件
(b)第二步:Spark Streaming程序
(c)第三步:注意除了需要使用Flume的lib的jar包以外,還需要以下jar包:
spark-streaming-flume_2.1.0.jar
(d)第四步:測試
- 啟動Spark Streaming程序
- 啟動Flume
- 拷貝日志文件到/root/training/logs目錄
- 觀察輸出,采集到數據
(2)基于Custom Sink的Pull模式
????不同于Flume直接將數據推送到Spark Streaming中,第二種模式通過以下條件運行一個正常的Flume sink。Flume將數據推送到sink中,并且數據保持buffered狀態。Spark Streaming使用一個可靠的Flume接收器和轉換器從sink拉取數據。只要當數據被接收并且被Spark Streaming備份后,轉換器才運行成功。
????這樣,與第一種模式相比,保證了很好的健壯性和容錯能力。然而,這種模式需要為Flume配置一個正常的sink。
以下為配置步驟:
(a)第一步:Flume的配置文件
(b)第二步:Spark Streaming程序
(c)第三步:需要的jar包
- 將Spark的jar包拷貝到Flume的lib目錄下
- 下面的這個jar包也需要拷貝到Flume的lib目錄下,同時加入IDEA工程的classpath
spark-streaming-flume-sink_2.1.0.jar
(d)第四步:測試
- 啟動Flume
- 在IDEA中啟動FlumeLogPull
- 將測試數據拷貝到/root/training/logs
- 觀察IDEA中的輸出
2.Spark Streaming接收Kafka數據
Apache Kafka是一種高吞吐量的分布式發布訂閱消息系統。
(1)搭建ZooKeeper(Standalone):
(a)配置/root/training/zookeeper-3.4.10/conf/zoo.cfg文件
dataDir=/root/training/zookeeper-3.4.10/tmp
server.1=spark81:2888:3888
(b)在/root/training/zookeeper-3.4.10/tmp目錄下創建一個myid的空文件
echo 1 > /root/training/zookeeper-3.4.6/tmp/myid
(2)搭建Kafka環境(單機單broker):
(a)修改server.properties文件
(b)啟動Kafka
bin/kafka-server-start.sh config/server.properties &
出現以下錯誤:
需要修改bin/kafka-run-class.sh文件,將這個選項注釋掉。
(c)測試Kafka
- 創建Topic
bin/kafka-topics.sh --create --zookeeper spark81:2181 -replication-factor 1 --partitions 3 --topic mydemo1
- 發送消息
bin/kafka-console-producer.sh --broker-list spark81:9092 --topic mydemo1
- 接收消息
bin/kafka-console-consumer.sh --zookeeper spark81:2181 --topic mydemo1
(3)搭建Spark Streaming和Kafka的集成開發環境
????由于Spark Streaming和Kafka集成的時候,依賴的jar包比較多,而且還會產生沖突。強烈建議使用Maven的方式來搭建項目工程。
下面是依賴的pom.xml文件:
(4)基于Receiver的方式
????這個方法使用了Receivers來接收數據。Receivers的實現使用到Kafka高層次的消費者API。對于所有的Receivers,接收到的數據將會保存在Spark executors中,然后由Spark Streaming啟動的Job來處理這些數據。
(a)開發Spark Streaming的Kafka Receivers
(b)測試
- 啟動Kafka消息的生產者
bin/kafka-console-producer.sh --broker-list spark81:9092 --topic mydemo1
- 在IDEA中啟動任務,接收Kafka消息
(5)直接讀取方式
????和基于Receiver接收數據不一樣,這種方式定期地從Kafka的topic+partition中查詢最新的偏移量,再根據定義的偏移量范圍在每個batch里面處理數據。當作業需要處理的數據來臨時,spark通過調用Kafka的簡單消費者API讀取一定范圍的數據。
(a)開發Spark Streaming的程序
(b)測試
- 啟動Kafka消息的生產者
bin/kafka-console-producer.sh --broker-list spark81:9092 --topic mydemo1
- 在IDEA中啟動任務,接收Kafka消息
四.性能優化
1、減少批數據的執行時間
在Spark中有幾個優化可以減少批處理的時間:
(1)數據接收的并行水平
????通過網絡(如kafka,flume,socket等)接收數據需要這些數據反序列化并被保存到Spark中。如果數據接收成為系統的瓶頸,就要考慮并行地接收數據。注意,每個輸入DStream創建一個receiver(運行在worker機器上)接收單個數據流。創建多個輸入DStream并配置它們可以從源中接收不同分區的數據流,從而實現多數據流接收。例如,接收兩個topic數據的單個輸入DStream可以被切分為兩個kafka輸入流,每個接收一個topic。這將在兩個worker上運行兩個receiver,因此允許數據并行接收,提高整體的吞吐量。多個DStream可以被合并生成單個DStream,這樣運用在單個輸入DStream的transformation操作可以運用在合并的DStream上。
(2)數據處理的并行水平
????如果運行在計算stage上的并發任務數不足夠大,就不會充分利用集群的資源。默認的并發任務數通過配置屬性來確定spark.default.parallelism。
(3)數據序列化
????可以通過改變序列化格式來減少數據序列化的開銷。在流式傳輸的情況下,有兩種類型的數據會被序列化:
- 輸入數據
- 由流操作生成的持久RDD
????在上述兩種情況下,使用Kryo序列化格式可以減少CPU和內存開銷。
2.設置正確的批容量
????為了Spark Streaming應用程序能夠在集群中穩定運行,系統應該能夠以足夠的速度處理接收的數據(即處理速度應該大于或等于接收數據的速度)。這可以通過流的網絡UI觀察得到。批處理時間應該小于批間隔時間。
????根據流計算的性質,批間隔時間可能顯著的影響數據處理速率,這個速率可以通過應用程序維持。可以考慮WordCountNetwork這個例子,對于一個特定的數據處理速率,系統可能可以每2秒打印一次單詞計數(批間隔時間為2秒),但無法每500毫秒打印一次單詞計數。所以,為了在生產環境中維持期望的數據處理速率,就應該設置合適的批間隔時間(即批數據的容量)。
????找出正確的批容量的一個好的辦法是用一個保守的批間隔時間(5-10,秒)和低數據速率來測試你的應用程序。
3.內存調優
在這一節,重點介紹幾個強烈推薦的自定義選項,它們可以減少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:
????使用并發的標記-清除垃圾回收可以進一步減少垃圾回收的暫停時間。盡管并發的垃圾回收會減少系統的整體吞吐量,但是仍然推薦使用它以獲得更穩定的批處理時間。