Spark官網補缺之SparkStreaming

Spark官網補缺之SparkStreaming

版本2.3.4 官網網址:http://spark.apache.org/docs/2.3.4/streaming-programming-guide.html

1.開篇簡介

Spark Streaming是核心Spark API的擴展,可實現實時數據流的可伸縮,高吞吐量,容錯流處理。數據可以從像,kafka,flume,hdfs/s3,kinesis,TCP套接字許多來源攝入,並且可以使用與像高級別功能表達複雜的算法來處理`map`,`reduce`,`join`和`window`。最後,可以將處理後的數據推送到文件系統,數據庫和實時儀表板。實際上,您可以在數據流上應用Spark的 [機器學習]和 [圖形處理]算法。

在這裏插入圖片描述

在內部,它的工作方式如下。Spark Streaming接收實時輸入數據流,並將數據分成批處理,然後由Spark引擎進行處理,以分批生成最終結果流。
Spark Streaming提供了稱爲離散流或DStream的高級抽象,它表示連續的數據流。可以根據來自Kafka,Flume和Kinesis等來源的輸入數據流來創建DStream,也可以通過對其他DStream應用高級操作來創建DStream。在內部,DStream表示爲RDD序列 。

在這裏插入圖片描述

1.1隱式轉換

首先,我們將Spark Streaming類的名稱以及從StreamingContext進行的一些隱式轉換導入到我們的環境中
import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._  //not necessary since Spark 1.3

1.2創建StreamingContext

該appName參數是您的應用程序顯示在集羣UI上的名稱。 master是Spark,Mesos或YARN羣集URL或特殊的"local [*]”字符串,以在本地模式下運行。實際上,當在集羣上運行時,您將不希望master在程序中進行硬編碼,而是在其中啓動應用程序spark-submit並在其中接收。但是,對於本地測試和單元測試,您可以傳遞“ local [*]”以在內部運行Spark Streaming(檢測本地系統中的內核數)。請注意,這會在內部創建一個SparkContext(所有Spark功能的起點),可以通過訪問ssc.sparkContext。
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(conf, Seconds(1))

1.3pow依賴

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming_2.11</artifactId>
    <version>2.3.4</version>
    <scope>provided</scope>
</dependency>
要從Spark Streaming核心API中不存在的,從諸如Kafka,Flume和Kinesis之類的源中獲取數據,則必須將相應的工件添加`spark-streaming-xyz_2.11`到依賴項中。例如,一些常見的如下。
Source Artifact
Kafka spark-streaming-kafka-0-10_2.11
Flume spark-streaming-flume_2.11
Kinesis spark-streaming-kinesis-asl_2.11 [Amazon Software License]

1.4一些注意點

定義上下文之後,您必須執行以下操作。
通過創建輸入DStreams來定義輸入源。
通過對DStreams應用轉換和輸出操作來定義流計算。
開始接收數據並使用streamingContext.start()進行處理。
使用streamingContext.awaitTermination()等待處理停止(手動或由於任何錯誤)。
可以使用streamingContext.stop()手動停止處理。
指出,記住:
一旦上下文啓動,就不能設置或添加新的流計算。
上下文一旦停止,就不能重新啓動。
JVM中只能同時激活一個StreamingContext。
StreamingContext上的stop()也會停止SparkContext。
要僅停止StreamingContext,請將名爲stopSparkContext的stop()的可選參數設置爲false。
只要在創建下一個StreamingContext之前停止前一個StreamingContext(不停止SparkContext),就可以重用SparkContext來創建多個StreamingContext。

1.5DStreams

離散流(DStreams)
離散流或DStream是Spark Streaming提供的基本抽象。它表示連續的數據流,可以是從源接收的輸入數據流,也可以是通過轉換輸入流生成的已處理數據流。在內部,DStream由一系列連續的RDD表示,這是Spark對不可變的分佈式數據集的抽象(有關更多詳細信息,請參見Spark編程指南)。DStream中的每個RDD都包含來自特定間隔的數據,如下圖所示。

在這裏插入圖片描述

在DStream上執行的任何操作都轉換爲對基礎RDD的操作。例如,在之前的將行流轉換爲單詞的案例中,將`flatMap`操作應用於`lines`DStream中的每個RDD 以生成DStream的 `words`RDD。如下圖所示。

在這裏插入圖片描述

這些基礎的RDD轉換由Spark引擎計算。DStream操作隱藏了大多數這些細節,併爲開發人員提供了更高級別的API,以方便使用。這些操作將在後面的部分中詳細討論。

1.6輸入和Receiver

輸入DStream是表示從流源接收的輸入數據流的DStream。在快速示例中,lines輸入DStream代表從netcat服務器接收的數據流。每個輸入DStream(文件流除外,本節稍後將討論)都與一個Receiver對象 (Scala doc, Java doc)關聯,該對象從源接收數據並將其存儲在Spark的內存中以進行處理。

Spark Streaming提供了兩類內置的流媒體源。
基本來源:可直接在StreamingContext API中獲得的來源。示例: file systems, and socket connections.
高級資源:可以通過額外的實用程序類獲得諸如Kafka,Flume,Kinesis等資源。如鏈接部分所述,它們需要針對額外的依賴項進行鏈接。
請注意,如果要在流應用程序中並行接收多個數據流,則可以創建多個輸入DStream(在“ 性能調整”部分中進一步討論)。這將創建多個接收器,這些接收器將同時接收多個數據流。但是請注意,Spark工作者/執行程序是一項長期運行的任務,因此它佔用了分配給Spark Streaming應用程序的核心之一。因此,重要的是要記住,需要爲Spark Streaming應用程序分配足夠的內核(或線程,如果在本地運行),以處理接收到的數據以及運行接收器。

1.6.1 file systems

要從與HDFS API兼容的任何文件系統(即HDFS,S3,NFS等)上的文件中讀取數據,可以通過創建DStream StreamingContext.fileStream[KeyClass, ValueClass, InputFormatClass]。

文件流不需要運行Receiver,因此無需分配任何內核來接收文件數據。

對於簡單的文本文件,最簡單的方法是StreamingContext.textFileStream(dataDirectory)。

如何監控目錄:

Spark Streaming將監視目錄dataDirectory並處理在該目錄中創建的所有文件
1 可以監視一個簡單目錄,例如"hdfs://namenode:8040/logs/"。發現後,將直接處理該路徑下的所有文件。
2 甲POSIX glob模式可以被提供,例如 "hdfs://namenode:8040/logs/2017/*"。在此,DStream將包含與模式匹配的目錄中的所有文件。也就是說:這是監控目錄的模式,而不是目錄中的文件的模式(即文件內容的增加,不會有數據的產生)。
3 所有文件都必須使用相同的數據格式。
4 根據文件的修改時間而非創建時間,將其視爲時間段的一部分。處理後,在當前窗口中對文件的更改將不會導致重新讀取該文件。也就是說:忽略更新。
5 目錄下的文件越多,掃描更改所需的時間就越長-即使未修改任何文件。
6 如果使用通配符來標識目錄(例如)"hdfs://namenode:8040/logs/2016-*",則重命名整個目錄以匹配路徑會將目錄添加到受監視目錄列表中。流中僅包含目錄中修改時間在當前窗口內的文件。
7 調用FileSystem.setTimes() 修復時間戳是一種在以後的窗口中拾取文件的方法,即使其內容沒有更改。

1.6.2使用對象存儲作爲數據源

HDFS之類的“完整”文件系統往往會在創建輸出流後立即對其文件設置修改時間。當打開文件時,甚至在完全寫入數據之前,該文件也可能包含在`DStream`-之後,將忽略同一窗口中對該文件的更新。也就是說:更改可能會丟失,流中會省略數據。

爲了確保在窗口中可以接收到更改,請將文件寫入一個不受監視的目錄,然後在關閉輸出流後立即將其重命名爲目標目錄。如果重命名的文件在創建窗口期間顯示在掃描的目標目錄中,則將提取新數據。

相反,由於實際複製了數據,因此諸如Amazon S3和Azure存儲之類的對象存儲通常具有較慢的重命名操作。此外,重命名的對象可能具有`rename()`操作時間作爲其修改時間,因此可能不被視爲原始創建時間所暗示的窗口部分。

需要對目標對象存儲進行仔細的測試,以驗證存儲的時間戳行爲與Spark Streaming期望的一致。直接寫入目標目錄可能是通過所選對象存儲流傳輸數據的適當策略。

1.6.3自定義接收器

首先從實現Receiver (Scala doc, Java doc)開始。自定義接收方必須通過實現兩個方法來擴展此抽象類
onStart():開始接收數據需要做的事情。
onStop():停止接收數據的操作。
雙方onStart()並onStop()不能無限期地阻塞。通常,onStart()將啓動負責接收數據的線程,並onStop()確保這些線程停止接收數據。接收線程也可以使用isStopped(),一個Receiver方法,以檢查他們是否應該停止接收數據。

接收到數據後,可以通過調用將該數據存儲在Spark內部store(data),這是Receiver類提供的方法。有多種類型,store()它們可以一次存儲記錄的接收數據,也可以存儲爲對象/序列化字節的完整集合。請注意,store()用於實現接收器的風格 會影響其可靠性和容錯語義。稍後將對此進行詳細討論。

接收線程中的任何異常都應被捕獲並正確處理,以避免接收器出現靜默故障。restart(<exception>)將通過異步調用onStop()然後onStart()延遲後調用來重新啓動接收器。 stop(<exception>)將呼叫onStop()並終止接收器。此外,reportError(<error>) 無需停止/重新啓動接收器即可向驅動程序報告錯誤消息(在日誌和UI中可見)。

以下是一個自定義接收器,它通過套接字接收文本流。它將文本流中以'\ n'分隔的行視爲記錄,並將其存儲在Spark中。如果接收線程在連接或接收時出現任何錯誤,則重新啓動接收器以進行另一次連接嘗試。
class CustomReceiver(host: String, port: Int)
  extends Receiver[String](StorageLevel.MEMORY_AND_DISK_2) with Logging {

  def onStart() {
    // Start the thread that receives data over a connection
    new Thread("Socket Receiver") {
      override def run() { receive() }
    }.start()
  }

  def onStop() {
    // There is nothing much to do as the thread calling receive()
    // is designed to stop by itself if isStopped() returns false
  }

  /** Create a socket connection and receive data until receiver is stopped */
  private def receive() {
    var socket: Socket = null
    var userInput: String = null
    try {
      // Connect to host:port
      socket = new Socket(host, port)

      // Until stopped or connection broken continue reading
      val reader = new BufferedReader(
        new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))
      userInput = reader.readLine()
      while(!isStopped && userInput != null) {
        store(userInput)
        userInput = reader.readLine()
      }
      reader.close()
      socket.close()

      // Restart in an attempt to connect again when server is active again
      restart("Trying to connect again")
    } catch {
      case e: java.net.ConnectException =>
        // restart if could not connect to server
        restart("Error connecting to " + host + ":" + port, e)
      case t: Throwable =>
        // restart if there is any other error
        restart("Error receiving data", t)
    }
  }
}
在Spark Streaming應用程序中使用自定義接收器
可以通過使用自定義接收器在Spark Streaming應用程序中使用 streamingContext.receiverStream(<instance of custom receiver>)。這將使用自定義接收器實例接收的數據創建輸入DStream,如下所示:

斯卡拉
爪哇
// Assuming ssc is the StreamingContext
val customReceiverStream = ssc.receiverStream(new CustomReceiver(host, port))
val words = customReceiverStream.flatMap(_.split(" "))
...
完整的源代碼在示例CustomReceiver.scala中。
如Spark流編程指南中簡要討論的,基於可靠性和容錯語義,
有兩種接收器。
可靠的接收方——對於允許發送數據被確認的可靠源,可靠的接收方正確地向源確認數據已被接收並可靠地存儲在Spark中(即已成功複製)。通常,實現此接收器需要仔細考慮源確認的語義。
不可靠的接收方——不可靠的接收方不向源發送確認。這可以用於不支持確認的源,甚至可以用於不希望或不需要進行復雜確認的可靠源。
要實現可靠的接收器,必須使用store(多記錄)來存儲數據。這種類型的存儲是一個阻塞調用,它只在所有給定的記錄都存儲在Spark中之後才返回。如果配置的接收器存儲級別使用複製(默認啓用),則此調用在複製完成後返回。因此,它確保數據被可靠地存儲,並且接收者現在可以適當地確認源。這確保了在複製數據的過程中,當接收方發生故障時,不會丟失任何數據。
不可靠的接收方不必實現這些邏輯。它可以簡單地從源接收記錄,然後使用store(單記錄)一次插入一條記錄。雖然它沒有獲得存儲(多記錄)的可靠性保證,
但它具有以下優點:系統負責將數據分塊成適當大小的塊(在Spark流編程指南中查找塊間隔)。如果指定了速率限制,則系統負責控制接收速率。
由於這兩個原因,不可靠的接收器比可靠的接收器更容易實現。下表總結了兩種類型的接收器的特點
接收器類型 特點
不可靠的接收者 易於實現。 系統負責塊的生成和速率控制。沒有容錯保證,會因接收器故障而丟失數據。
可靠的接收者 強大的容錯保證,可以確保零數據丟失。 接收器實現要處理的塊生成和速率控制。 實現的複雜性取決於源的確認機制。

1.6.4RDD排隊作爲流

爲了使用測試數據測試Spark Streaming應用程序,還可以使用,基於RDD隊列創建DStream `streamingContext.queueStream(queueOfRDDs)`。推送到隊列中的每個RDD將被視爲DStream中的一批數據,並像流一樣進行處理。

1.7Dstream的轉換

Transformation Meaning
map(func) 通過將源DStream的每個元素傳遞給函數func來返回新的DStream 。
flatMap(func) 與map相似,但是每個輸入項可以映射到0個或多個輸出項。
filter(func) 通過僅選擇func返回true 的源DStream的記錄來返回新的DStream 。
repartition(numPartitions) 通過創建更多或更少的分區來更改此DStream中的並行度。
union(otherStream) 返回一個新的DStream,其中包含源DStream和otherDStream中的元素的並
count() 通過計算源DStream的每個RDD中的元素數,返回一個新的單元素RDD DStream。
reduce(func) 通過使用函數func(帶有兩個參數並返回一個)來聚合源DStream的每個RDD中的元素,從而返回一個單元素RDD的新DStream 。該函數應具有關聯性和可交換性,以便可以並行計算。
countByValue() 在類型爲K的元素的DStream上調用時,返回一個新的(K,Long)對的DStream,其中每個鍵的值是其在源DStream的每個RDD中的頻率。
reduceByKey(func, [numTasks]) 在(K,V)對的DStream上調用時,返回一個新的(K,V)對的DStream,其中使用給定的reduce函數聚合每個鍵的值。**注意:**默認情況下,這使用Spark的默認並行任務數(本地模式爲2,而在集羣模式下,此數量由config屬性確定spark.default.parallelism)進行分組。您可以傳遞一個可選numTasks參數來設置不同數量的任務。
join(otherStream, [numTasks]) 當在(K,V)和(K,W)對的兩個DStream上調用時,返回一個新的(K,(V,W))對的DStream,其中每個鍵都有所有元素對。
cogroup(otherStream, [numTasks]) 在(K,V)和(K,W)對的DStream上調用時,返回一個新的(K,Seq [V],Seq [W])元組的DStream。
transform(func) 通過對源DStream的每個RDD應用RDD-to-RDD函數來返回新的DStream。這可用於在DStream上執行任意RDD操作。
updateStateByKey(func) 返回一個新的“狀態” DStream,在該DStream中,通過在鍵的先前狀態和鍵的新值上應用給定函數來更新每個鍵的狀態。這可用於維護每個鍵的任意狀態數據。

1.7.1UpdateStageByKey操作

該updateStateByKey操作使您可以保持任意狀態,同時不斷用新信息更新它。要使用此功能,您將必須執行兩個步驟。

定義狀態-狀態可以是任意數據類型。
定義狀態更新功能-使用功能指定如何使用輸入流中的先前狀態和新值來更新狀態。
在每個批次中,Spark都會對所有現有密鑰應用狀態更新功能,而不管它們是否在批次中具有新數據。如果更新函數返回,None則將刪除鍵值對。
   ssc.checkpoint("in\\checkpoint")
//ssc.checkpoint("hdfs://MyDis:9000/spark/checkpoint")
    //updateStageBykey算子,從最開始計算,需要checkPoint
    operWc.updateStateByKey(
      (nowBatch: Seq[Int], history: Option[Int]) => {
        Some(nowBatch.sum + history.getOrElse(0))
      }).print(100)

1.7.2轉換操作

該transform操作(以及類似的變體transformWith)允許將任意RDD-to-RDD功能應用於DStream。它可用於應用DStream API中未公開的任何RDD操作。例如,將數據流中的每個批次與另一個數據集連接在一起的功能未直接在DStream API中公開。但是,您可以輕鬆地使用transform它。這實現了非常強大的可能性。例如,可以通過將輸入數據流與預先計算的垃圾郵件信息(也可能由Spark生成)結合在一起,然後基於該信息進行過濾來進行實時數據清除。
val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...) // RDD containing spam information

val cleanedDStream = wordCounts.transform { rdd =>
  rdd.join(spamInfoRDD).filter(...) // join data stream with spam information to do data cleaning
  ...
}
}
請注意,在每個批處理間隔中都會調用提供的函數。這使您可以執行隨時間變化的RDD操作,即可以在批之間更改RDD操作,分區數,廣播變量等。

1.7.3window操作

Spark Streaming還提供了窗口計算,可讓您在數據的滑動窗口上應用轉換。下圖說明了此滑動窗口。

火花流

如該圖所示,每當窗口滑動在源DSTREAM,落入窗口內的源RDDS被組合及操作以產生RDDS的窗DSTREAM。在這種特定情況下,該操作將應用於數據的最後3個時間單位,並以2個時間單位滑動。這表明任何窗口操作都需要指定兩個參數。

窗口長度 - 窗口的持續時間(圖中3)。
滑動間隔 -進行窗口操作的間隔(圖中爲2)。
這兩個參數必須是源DStream的批處理間隔的倍數(圖中爲1)。

讓我們用一個例子來說明窗口操作。假設您要擴展 前面的示例,方法是每10秒在數據的最後30秒生成一次字數統計。爲此,我們必須在最後30秒的數據reduceByKey上對pairsDStream (word, 1)對應用該操作。這是通過操作完成的reduceByKeyAndWindow。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-p3YCVFpI-1577268000222)(D:\新機\千峯筆記\md_png\image-20191213114041812.png)]

// Reduce last 30 seconds of data, every 10 seconds
val windowedWordCounts = pairs.reduceByKeyAndWindow((a:Int,b:Int) => (a + b), Seconds(30), Seconds(10))
windowwindowLengthslideInterval 返回基於源DStream的窗口批處理計算的新DStream。
countByWindowwindowLengthslideInterval 返回流中元素的滑動窗口計數。
reduceByWindowfuncwindowLengthslideInterval 返回一個新的單元素流,該流是通過使用func在滑動間隔內聚合流中的元素而創建的。該函數應該是關聯的和可交換的,以便可以並行正確地計算它。
reduceByKeyAndWindowfuncwindowLengthslideInterval,[ numTasks ]) 在(K,V)對的DStream上調用時,返回新的(K,V)對的DStream,其中使用給定的reduce函數func 在滑動窗口中的批處理上聚合每個鍵的值。**注意:**默認情況下,這使用Spark的默認並行任務數(本地模式爲2,而在集羣模式下,此數量由config屬性確定spark.default.parallelism)進行分組。您可以傳遞一個可選 numTasks參數來設置不同數量的任務。
reduceByKeyAndWindowfuncinvFuncwindowLengthslideInterval,[ numTasks ]) 上述方法的一種更有效的版本,reduceByKeyAndWindow()其中,使用前一個窗口的減少值遞增地計算每個窗口的減少值。這是通過減少進入滑動窗口的新數據並“逆向減少”離開窗口的舊數據來完成的。一個示例是在窗口滑動時“增加”和“減少”鍵的計數。但是,它僅適用於“可逆歸約函數”,即具有對應的“逆歸約”函數(作爲參數invFunc)的歸約函數。像in中一樣reduceByKeyAndWindow,reduce任務的數量可以通過可選參數配置。請注意,必須啓用檢查點才能使用此操作。
countByValueAndWindowwindowLengthslideInterval,[ numTasks ]) 在(K,V)對的DStream上調用時,返回新的(K,Long)對的DStream,其中每個鍵的值是其在滑動窗口內的頻率。像in中一樣 reduceByKeyAndWindow,reduce任務的數量可以通過可選參數配置。

1.7.4join操作

最後,值得一提的是,您可以輕鬆地在Spark Streaming中執行各種類型的join。
1.7.4.1Dstream Dstream join
val stream1: DStream[String, String] = ...
val stream2: DStream[String, String] = ...
val joinedStream = stream1.join(stream2)
在此,在每個批處理間隔中,由生成的RDD stream1將與生成的RDD合併在一起stream2。你也可以做leftOuterJoin,rightOuterJoin,fullOuterJoin。此外,在流的窗口上進行連接通常非常有用。這也很容易。
val windowedStream1 = stream1.window(Seconds(20))
val windowedStream2 = stream2.window(Minutes(1))
val joinedStream = windowedStream1.join(windowedStream2)
1.7.4.2流RDD聯接
val dataset: RDD[String, String] = ...
val windowedStream = stream.window(Seconds(20))...
val joinedStream = windowedStream.transform { rdd => rdd.join(dataset) }

1.8Dstreams的輸出操作

輸出操作允許將DStream的數據推出到外部系統,例如數據庫或文件系統。由於輸出操作實際上允許外部系統使用轉換後的數據,因此它們會觸發所有DStream轉換的實際執行(類似於RDD的操作)。當前,定義了以下輸出操作:
輸出操作 含義
print() 在運行流應用程序的驅動程序節點上,打印DStream中每批數據的前十個元素。這對於開發和調試很有用。 Python API在Python API中稱爲 pprint()
saveAsTextFiles(prefix, [suffix]) 將此DStream的內容另存爲文本文件。基於產生在每批間隔的文件名的前綴後綴“前綴TIME_IN_MS [.suffix]”
saveAsObjectFiles(prefix, [suffix]) 將此DStream的內容保存爲SequenceFiles序列化Java對象的內容。基於產生在每批間隔的文件名的前綴後綴“前綴TIME_IN_MS [.suffix]”Python API這在Python API中不可用。
saveAsHadoopFiles(prefix, [suffix]) 將此DStream的內容另存爲Hadoop文件。基於產生在每批間隔的文件名的前綴後綴“前綴TIME_IN_MS [.suffix]”Python API這在Python API中不可用。
foreachRDDfunc 最通用的輸出運算符,將函數func應用於從流生成的每個RDD。此功能應將每個RDD中的數據推送到外部系統,例如將RDD保存到文件或通過網絡將其寫入數據庫。請注意,函數func在運行流應用程序的驅動程序進程中執行,並且通常在其中具有RDD操作,這將強制計算流RDD。

1.8.1使用foreachRDD的設計模式

dstream.foreachRDD`是一個強大的原語,可以將數據發送到外部系統。但是,重要的是要了解如何正確有效地使用此原語。應避免的一些常見錯誤如下。

通常,將數據寫入外部系統需要創建一個連接對象(例如,到遠程服務器的TCP連接),並使用該對象將數據發送到遠程系統。爲此,開發人員可能會無意間嘗試在Spark驅動程序中創建連接對象,然後嘗試在Spark worker中使用該對象以將記錄保存在RDD中。例如(在Scala中),
dstream.foreachRDD { rdd =>
  val connection = createNewConnection()  // executed at the driver
  rdd.foreach { record =>
    connection.send(record) // executed at the worker
  }
}
這是不正確的,因爲這要求將連接對象序列化並從驅動程序發送給工作程序。這樣的連接對象很少能在機器之間轉移。此錯誤可能表現爲序列化錯誤(連接對象不可序列化),初始化錯誤(連接對象需要在工作程序中初始化)等。正確的解決方案是在工作程序中創建連接對象。
但是,這可能會導致另一個常見錯誤-爲每個記錄創建一個新的連接。例如,
dstream.foreachRDD { rdd =>
  rdd.foreach { record =>
    val connection = createNewConnection()
    connection.send(record)
    connection.close()
  }
}
通常,創建連接對象會浪費時間和資源。因此,爲每個記錄創建和銷燬連接對象會導致不必要的高開銷,並且會大大降低系統的整體吞吐量。更好的解決方案是使用 rdd.foreachPartition-創建單個連接對象,並使用該連接在RDD分區中發送所有記錄。
dstream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
    val connection = createNewConnection()
    partitionOfRecords.foreach(record => connection.send(record))
    connection.close()
  }
}
這將分攤許多記錄上的連接創建開銷。

最後,可以通過在多個RDD /批次之間重用連接對象來進一步優化。與將多個批次的RDD推送到外部系統時可以重用的連接對象相比,它可以維護一個靜態的連接對象池,從而進一步減少了開銷。
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
  }
}
請注意,應按需延遲創建池中的連接,如果一段時間不使用,則超時。這樣可以保證最有效地將數據發送到外部系統。
其他要記住的要點:
DStream由輸出操作延遲執行,就像RDD由RDD操作延遲執行一樣。具體來說,DStream輸出操作內部的RDD動作會強制處理接收到的數據。因此,如果您的應用程序沒有任何輸出操作,或者dstream.foreachRDD()內部沒有任何RDD操作,就不會執行任何輸出操作。系統將僅接收數據並將其丟棄。
默認情況下,輸出操作一次執行一次。它們按照在應用程序中定義的順序執行。

1.9DataFrame和SQL操作

您可以輕鬆地對流數據使用[DataFrames和SQL]操作。您必須使用StreamingContext使用的SparkContext創建一個SparkSession。此外,還可以在驅動程序失敗時重新啓動。這是通過創建SparkSession的延遲實例化單例實例來完成的。在下面的示例中顯示。它修改了前面的[單詞計數示例,]以使用DataFrames和SQL生成單詞計數。每個RDD都轉換爲一個DataFrame,註冊爲臨時表,然後使用SQL查詢。
/** DataFrame operations inside your streaming program */

val words: DStream[String] = ...

words.foreachRDD { rdd =>

  // Get the singleton instance of SparkSession
  val spark = SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate()
  import spark.implicits._

  // Convert RDD[String] to DataFrame
  val wordsDataFrame = rdd.toDF("word")

  // Create a temporary view
  wordsDataFrame.createOrReplaceTempView("words")

  // Do word count on DataFrame using SQL and print it
  val wordCountsDataFrame = 
    spark.sql("select word, count(*) as total from words group by word")
  wordCountsDataFrame.show()
}
您還可以在來自不同線程的流數據定義的表上運行SQL查詢(即與正在運行的StreamingContext異步)。只要確保將StreamingContext設置爲記住足夠的流數據即可運行查詢。否則,不知道任何異步SQL查詢的StreamingContext將在查詢完成之前刪除舊的流數據。例如,如果您要查詢最後一批,但是查詢可能需要5分鐘才能運行,然後調用streamingContext.remember(Minutes(5))(使用Scala或其他語言的等效語言)。

2.0緩存和序列化

與RDD相似,DStreams還允許開發人員將流的數據持久存儲在內存中。
也就是說,persist()在DStream上使用該方法將自動將該DStream的每個RDD持久存儲在內存中。如果DStream中的數據將被多次計算(例如,對同一數據進行多次操作),這將很有用。

對於和的基於窗口的操作reduceByWindow和 reduceByKeyAndWindow和的基於狀態的操作updateStateByKey,這都是隱含的。因此,由基於窗口的操作生成的DStream會自動保存在內存中,而無需開發人員調用persist()。

對於通過網絡接收數據的輸入流(例如,Kafka,Flume,套接字等),默認的持久性級別設置爲將數據複製到兩個節點以實現容錯。

2.1checkpoint

流式應用程序必須24/7全天候運行,因此必須對與應用程序邏輯無關的故障(例如系統故障,JVM崩潰等)具有彈性。爲此,Spark Streaming需要將足夠的信息檢查點指向容錯存儲系統,以便可以從故障中恢復。檢查點有兩種類型的數據。

元數據檢查點 -將定義流計算的信息保存到HDFS等容錯存儲中。這用於從運行流應用程序的驅動程序的節點的故障中恢復(稍後詳細討論)。
元數據包括:
配置——用於創建流應用程序的配置。
DStream操作——定義流應用程序的一組DStream操作。
未完成的批——其作業在隊列中但尚未完成的批。
數據檢查點: 
將生成的RDD保存到可靠的存儲中。在某些狀態轉換中,這是必須的,這些轉換將跨多
個批次的數據進行合併。在此類轉換中,生成的RDD依賴於先前批次的RDD,這導致依賴項鍊的長度隨時間不斷增加。爲了避免恢復時間的這種無限制的增加(與依存關係鏈成比例),有狀態轉換的中間RDD定期 檢查點到可靠的存儲(例如HDFS)以切斷依存關係鏈。
總而言之,從驅動程序故障中恢復時,主要需要元數據檢查點,而如果使用有狀態轉換,則即使是基本功能,也需要數據或RDD檢查點。

2.1.1何時啓用檢查點

必須爲具有以下任何要求的應用程序啓用檢查點:

- *有狀態轉換的用法* -如果在應用程序中使用`updateStateByKey`或`reduceByKeyAndWindow`(帶有反函數),則必須提供檢查點目錄以允許定期進行RDD檢查點。
- 從運行應用程序的驅動程序故障中恢復* -元數據檢查點用於恢復進度信息。

請注意,沒有前述狀態轉換的簡單流應用程序可以在不啓用檢查點的情況下運行。在這種情況下,從驅動程序故障中恢復也將是部分的(某些丟失但未處理的數據可能會丟失)。這通常是可以接受的,並且許多都以這種方式運行Spark Streaming應用程序。預計將來會改善對非Hadoop環境的支持。

2.1.2如何配置檢查點

可以通過在容錯,可靠的文件系統(例如,HDFS,S3等)中設置目錄來啓用檢查點,將檢查點信息保存到該目錄中。這是通過使用完成的streamingContext.checkpoint(checkpointDirectory)。這將允許您使用前面提到的有狀態轉換。此外,如果要使應用程序從驅動程序故障中恢復,則應重寫流應用程序以具有以下行爲。
程序首次啓動時,它將創建一個新的StreamingContext,設置所有流,然後調用start()。
失敗後重新啓動程序時,它將根據檢查點目錄中的檢查點數據重新創建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,
// irrespective of whether it is being started or restarted
context. ...

// Start the context
context.start()
context.awaitTermination()
如果checkpointDirectory存在,則將根據檢查點數據重新創建上下文。如果該目錄不存在(即首次運行),則將functionToCreateContext調用該函數以創建新上下文並設置DStreams。請參閱Scala示例 RecoverableNetworkWordCount。本示例將網絡數據的字數附加到文件中。
除了使用getOrCreate驅動程序外,還需要確保驅動程序進程在發生故障時自動重新啓動。這隻能通過用於運行應用程序的部署基礎結構來完成。這將在“ 部署”部分中進一步討論 。
請注意,RDD的檢查點會導致保存到可靠存儲的成本。這可能會導致RDD被檢查點的那些批次的處理時間增加。因此,需要仔細設置檢查點的間隔。在小批量(例如1秒)時,每批檢查點可能會大大降低操作吞吐量。相反,檢查點太少會導致沿襲和任務規模增加,這可能會產生不利影響。對於需要RDD檢查點的有狀態轉換,默認間隔爲批處理間隔的倍數,至少應爲10秒。可以使用設置 dstream.checkpoint(checkpointInterval)。通常,DStream的5-10個滑動間隔的檢查點間隔是一個很好的嘗試設置。
recDsteam.checkpoint(Seconds(10))

2.2累加器,廣播變量和檢查點

無法從Spark Streaming中的檢查點恢復累加器和廣播變量。如果啓用檢查點並同時使用“累加器”或“ 廣播”變量 ,則必須爲“ 累加器”和“ 廣播”變量創建延遲實例化的單例實例, 以便在驅動程序發生故障重新啓動後可以重新實例化它們。在下面的示例中顯示。
object WordBlacklist {

  @volatile private var instance: Broadcast[Seq[String]] = null

  def getInstance(sc: SparkContext): Broadcast[Seq[String]] = {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          val wordBlacklist = Seq("a", "b", "c")
          instance = sc.broadcast(wordBlacklist)
        }
      }
    }
    instance
  }
}

object DroppedWordsCounter {

  @volatile private var instance: LongAccumulator = null

  def getInstance(sc: SparkContext): LongAccumulator = {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          instance = sc.longAccumulator("WordsInBlacklistCounter")
        }
      }
    }
    instance
  }
}

wordCounts.foreachRDD { (rdd: RDD[(String, Int)], time: Time) =>
  // Get or register the blacklist Broadcast
  val blacklist = WordBlacklist.getInstance(rdd.sparkContext)
  // Get or register the droppedWordsCounter Accumulator
  val droppedWordsCounter = DroppedWordsCounter.getInstance(rdd.sparkContext)
  // Use blacklist to drop words and use droppedWordsCounter to count them
  val counts = rdd.filter { case (word, count) =>
    if (blacklist.value.contains(word)) {
      droppedWordsCounter.add(count)
      false
    } else {
      true
    }
  }.collect().mkString("[", ", ", "]")
  val output = "Counts at time " + time + " " + counts
})

3.運行部署

3.1jar提交

將應用程序JAR打包 -您必須將流式應用程序編譯爲JAR。如果spark-submit用於啓動應用程序,則無需在JAR中提供Spark和Spark Streaming。但是,如果您的應用程序使用高級資源(例如Kafka,Flume),則必須將它們鏈接到的額外工件及其依賴項打包在用於部署應用程序的JAR中。例如,使用的應用程序KafkaUtils 必須spark-streaming-kafka-0-10_2.11在其應用程序JAR中包含及其所有傳遞依賴項。

3.2應用程序驅動程序的自動重新啓動

配置應用程序驅動程序的自動重新啓動 -若要從驅動程序故障中自動恢復,用於運行流式應用程序的部署基礎結構必須監視驅動程序進程,並在驅動程序失敗時重新啓動。不同的集羣管理器 具有不同的工具來實現這一目標。
Spark Standalone-可以提交Spark應用程序驅動程序以在Spark Standalone集羣內運行(請參閱 集羣部署模式),即,應用程序驅動程序本身在工作程序節點之一上運行。此外,可以指示獨立羣集管理器監督驅動程序,並在驅動程序由於非零退出代碼或由於運行該驅動程序的節點故障而失敗時重新啓動它。有關更多詳細信息,請參見Spark Standalone指南中的集羣模式和監督。
YARN -Yarn支持自動重啓應用程序的類似機制。
Mesos -已經使用Mesos來實現這一目標。

3.3預寫日誌

配置預寫日誌-自Spark 1.2起,我們引入了預*寫日誌*以實現強大的容錯保證。如果啓用,則將從接收器接收的所有數據寫入配置檢查點目錄中的預寫日誌。這樣可以防止驅動程序恢復時丟失數據,從而確保零數據丟失(在容錯語義部分中進行了詳細討論 )。這可以通過設置來啓用
配置參數spark.streaming.receiver.writeAheadLog.enable爲true。
但是,這些更強的語義可能會以單個接收器的接收吞吐量爲代價。可以通過[並行]運行[更多接收器]來糾正此問題 增加總吞吐量。
此外,由於啓用了預寫日誌,因此建議禁用Spark中接收到的數據的複製,因爲該日誌已存儲在複製的存儲系統中。可以通過將輸入流的存儲級別設置爲來完成此操作
StorageLevel.MEMORY_AND_DISK_SER
有關更多詳細信息,請參見請注意,啓用I/O加密後,Spark不會加密寫入預寫日誌的數據。如果需要對預寫日誌數據進行加密,則應將其存儲在本地支持加密的文件系統中。

3.4最大速率設置

設置最大接收速率 -如果羣集資源不足以使流應用程序能夠以最快的速度處理數據,則可以通過設置記錄/秒的最大速率限制來限制接收器的速率。
spark.streaming.receiver.maxRate 
spark.streaming.kafka.maxRatePerPartition
請參閱接收器和 Direct Kafka方法的配置參數 。在Spark 1.5中,我們引入了一個稱爲背壓的功能,該功能消除了設置此速率限制的需要,因爲Spark Streaming會自動計算出速率限制,並在處理條件發生變化時動態調整它們。這個背壓可以通過設置來啓用配置參數來。
spark.streaming.backpressure.enabled true

3.5監控應用

除了Spark的監視功能外,Spark Streaming還具有其他特定功能。使用StreamingContext時, Spark Web UI會顯示一個附加Streaming選項卡,其中顯示有關正在運行的接收器(接收器是否處於活動狀態,接收到的記錄數,接收器錯誤等)和已完成的批處理(批處理時間,排隊延遲等)的統計信息。 )。這可用於監視流應用程序的進度。
Web UI中的以下兩個指標特別重要:
處理時間 -處理每批數據的時間。
調度延遲 -批處理在隊列中等待先前批處理完成的時間。
如果批處理時間始終大於批處理時間間隔和/或排隊延遲不斷增加,則表明系統無法像生成批處理一樣快地處理批處理,並且落後於此。在這種情況下,請考慮 減少批處理時間。
還可以使用StreamingListener界面監視Spark Streaming程序的進度,該界面可讓您獲取接收器狀態和處理時間。請注意,這是一個開發人員API,將來可能會得到改進(即,報告了更多信息)。

3.6性能監控

要在集羣上的Spark Streaming應用程序中獲得最佳性能,需要進行一些調整。本節說明了可以調整以提高應用程序性能的許多參數和配置。在較高級別上,您需要考慮兩件事:

通過有效使用羣集資源減少每批數據的處理時間。

設置正確的批處理大小,以便可以在接收到批處理數據後儘快對其進行處理(也就是說,數據處理與數據攝取保持同步)

3.6.1減少批處理時間

3.6.2數據接收中的並行度

通過網絡(例如Kafka,Flume,套接字等)接收數據需要對數據進行反序列化並將其存儲在Spark中。如果數據接收成爲系統的瓶頸,請考慮並行化數據接收。請注意,每個輸入DStream都會創建一個接收器(在工作計算機上運行),該接收器接收單個數據流。因此,可以通過創建多個輸入DStream並將其配置爲從源接收數據流的不同分區來實現接收多個數據流。例如,可以將接收兩個主題數據的單個Kafka輸入DStream拆分爲兩個Kafka輸入流,每個輸入流僅接收一個主題。這將運行兩個接收器,從而允許並行接收數據,從而提高了總體吞吐量。這些多個DStream可以結合在一起以創建單個DStream。然後,可以將應用於單個輸入DStream的轉換應用於統一流。這樣做如下。
val numStreams = 5
val kafkaStreams = (1 to numStreams).map { i => KafkaUtils.createStream(...) }
val unifiedStream = streamingContext.union(kafkaStreams)
unifiedStream.print()
創建一個統一的DStream從多個DStreams的相同類型和相同的滑動持續時間。
  def union[T: ClassTag](streams: Seq[DStream[T]]): DStream[T] = withScope {
    new UnionDStream[T](streams.toArray)
  }
應該考慮的另一個參數是接收機的塊間隔,該間隔由配置參數確定 spark.streaming.blockInterval。對於大多數接收器,接收到的數據在存儲在Spark內存中之前會合併爲數據塊。每批中的塊數確定了將在類似map的轉換中用於處理接收到的數據的任務數。每批接收器中每個接收器的任務數大約爲(批處理間隔/塊間隔)。例如,200 ms的塊間隔將每2秒批處理創建10個任務。如果任務數太少(即少於每臺計算機的核心數),那麼它將效率低下,因爲將不使用所有可用的核心來處理數據。要增加給定批處理間隔的任務數,請減小阻止間隔。但是,建議的塊間隔最小值約爲50毫秒,在此之下,任務啓動開銷可能是個問題。
使用多個輸入流/接收器接收數據的一種替代方法是顯式地對輸入數據流進行分區(使用inputStream.repartition(<number of partitions>))。在進一步處理之前,這會將接收到的數據批分佈在羣集中指定數量的計算機上。

3.6.3數據處理中的並行度

如果在計算的任何階段使用的並行任務數量不夠高,則羣集資源可能無法得到充分利用。例如,對於像reduceByKey 和這樣的分佈式歸約操作reduceByKeyAndWindow,並行任務的默認數量由spark.default.parallelism configuration屬性控制。您可以將並行性級別作爲參數傳遞(請參閱 PairDStreamFunctions 文檔),或將spark.default.parallelism 配置屬性設置爲更改默認值。

4.Spark Streaming + Kafka集成指南

在這只是列出一部分,關於與kafka集成詳細信息

spark-streaming-kafka-0-8

spark-streaming-kafka-0-10

Apache Kafka是一種發佈-訂閱消息傳遞,被重新認爲是一種分佈式的,分區的,複製的提交日誌服務。在使用Spark開始集成之前,請仔細閱讀Kafka文檔。

Kafka項目在版本0.8和0.10之間引入了新的使用者API,因此有2個單獨的相應Spark Streaming包可用。請爲您的經紀人和所需功能選擇正確的軟件包;請注意,0.8集成與以後的0.9和0.10代理兼容,但0.10集成與早期的代理不兼容。

注意:自Spark 2.3.0起已不再支持Kafka 0.8。

spark-streaming-kafka-0-8 spark-streaming-kafka-0-10
經紀人版本 0.8.2.1或更高 0.10.0或更高
API成熟度 不推薦使用 穩定
語言支援 Scala,Java,Python Java Scala
接收器DStream 沒有
直接DStream
SSL / TLS支持 沒有
抵銷提交API 沒有
動態主題訂閱 沒有

spark整合es寫入:

object Save2EsLocalTest {

  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setAppName("save2eslocal").setMaster("local[*]")
    conf.set("spark.streaming.stopGracefullyOnShutdown","true")
    conf.set("es.index.auto.create", "false")
    conf.set("es.nodes", "127.0.0.1")
    conf.set("es.port", "9200")

    val sc = new SparkContext(conf)

    /*
    * es的參數
    *
    * es.resource.write  : index/type
    * es.write.operation :
    *                     index  加入新數據
    *                     upsert 數據不存在插入,數據存在更新
    *
    * es.mapping.id : 將document field 映射爲 document id
    *
    * */
    val config = scala.collection.mutable.Map("es.resource.write" -> "test/students","es.mapping.id"->"sid","es.write.operation"->"upsert")

    //必須引入
    import org.elasticsearch.spark._

    val students = sc.makeRDD(Seq(Map("sid"->"7","sname"->"hhy","sage"->100)))

    students.saveToEs(config)

    sc.stop()

  }

}

rue")
conf.set(“es.index.auto.create”, “false”)
conf.set(“es.nodes”, “127.0.0.1”)
conf.set(“es.port”, “9200”)

val sc = new SparkContext(conf)

/*
* es的參數
*
* es.resource.write  : index/type
* es.write.operation :
*                     index  加入新數據
*                     upsert 數據不存在插入,數據存在更新
*
* es.mapping.id : 將document field 映射爲 document id
*
* */
val config = scala.collection.mutable.Map("es.resource.write" -> "test/students","es.mapping.id"->"sid","es.write.operation"->"upsert")

//必須引入
import org.elasticsearch.spark._

val students = sc.makeRDD(Seq(Map("sid"->"7","sname"->"hhy","sage"->100)))

students.saveToEs(config)

sc.stop()

}

}


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