Spark Streaming概述
1. Spark Streaming是什麼
Spark Streaming用於流式
數據的處理。Spark Streaming支持的數據輸入源很多,例如:Kafka
、Flume
、Twitter
、ZeroMQ
和簡單的TCP
套接字等等。數據輸入後可以用Spark的高度抽象原語如:map、reduce、join、window等進行運算
。而結果也能保存在很多地方,如HDFS,數據庫等。
和Spark基於RDD的概念很相似,Spark Streaming使用離散化流
(discretized stream)作爲抽象表示,叫作DStream
。DStream 是隨時間推移而收到的數據的序列。在內部,每個時間區間(採集週期)收到的數據都作爲 RDD 存在,而DStream是由這些RDD所組成的序列(因此得名離散化
)。
2. Spark Streaming特點
- 易用
- 容錯
- 易整合到Spark體系
3. Spark Streaming架構
架構圖
多了一個接收器,一個StreamingContext。
整體架構圖:
形象圖
背壓機制
Spark 1.5以前
版本,用戶如果要限制Receiver
的數據接收速率,可以通過設置靜態配製參數spark.streaming.receiver.maxRate
的值來實現,此舉雖然可以通過限制接收速率,來適配當前的處理能力,防止內存溢出,但也會引入其它問題。比如:producer數據生產高於maxRate,當前集羣處理能力也高於maxRate,這就會造成資源利用率下降等問題。
爲了更好的協調數據接收速率與資源處理能力,1.5版本開始Spark Streaming可以動態控制數據接收速率
來適配集羣數據處理能力。背壓機制
(即Spark Streaming Backpressure
):
根據JobScheduler
反饋作業的執行信息來動態調整Receiver數據接收率。 通過屬性
spark.streaming.backpressure.enabled來控制是否啓用
backpressure`機制,默認值false,即不啓用。
第2章 Dstream入門
WordCount案例實操
需求
:使用netcat
工具向9999端口不斷的發送數據,通過SparkStreaming讀取端口數據並統計不同單詞出現的次數
添加依賴
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>${spark.version}</version>
<!--<scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>${spark.version}</version>
<!--<scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
<!--<scope>provided</scope>-->
</dependency>
</dependencies>
scala
package com.atguigu.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* Created by wuyufei on 06/09/2017.
*/
object WorldCount {
def main(args: Array[String]) {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
// 間隔採集週期, 若干半生類
val ssc = new StreamingContext(conf, Seconds(2))
ssc.checkpoint("./checkpoint")
// Create a DStream that will connect to hostname:port, like localhost:9999, 一行一行的接受數據
val lines: ReceiverInputDStream[String] = ssc.socketTextStream("localhost", 9000)
val linesFile: DStream[String] = ssc.textFileStream("test")
//監控文件夾裏的內容,然後從別的地方把文件移動到test中即可。不過Flume 也可以做並且做的更好, 一般不會用上述方法。
// Split each line into words
val words: DStream[String] = lines.flatMap(_.split(" "))
//import org.apache.spark.streaming.StreamingContext._ // not necessary since Spark 1.3
// Count each word in each batch
val pairs: DStream[(String, Int)] = words.map(word => (word, 1))
val wordCounts: DStream[(String, Int)] = pairs.reduceByKey(_ + _)
wordCounts.print()
// 啓動採集器
ssc.start()
// Driver等待採集器執行
ssc.awaitTermination()
//ssc.stop() // 把採集流停止 一般不用 因爲是不間斷的
}
}
測試:
[atguigu@hadoop102 spark]$ nc -lk 9000
hello sowhat
hello hello
hello hello
s s s
注意
:如果程序運行時,log日誌太多,可以將spark conf目錄下的log4j文件裏面的日誌級別改成WARN或者ERROR。
WordCount解析
Discretized Stream
是Spark Streaming的基礎抽象,代表持續性的數據流和經過各種Spark原語操作後的結果數據流。在內部實現上,DStream是一系列連續的RDD來表示。每個RDD含有一段時間間隔內的數據,如下圖:
第三章 Dstream創建
1. RDD隊列
用法及說明
測試過程中,可以通過使用ssc.queueStream(queueOfRDDs)來創建DStream,每一個推送到這個隊列中的RDD,都會作爲一個DStream處理。
需求
:間隔性的發送數據, 間隔性的從內存隊列取出數據,統計取出數據%10的結果個數
編寫代碼
package com.atguigu.streaming
import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.{Seconds, StreamingContext}
import scala.collection.mutable
/**
* 間隔性的發送數據, 間隔性的從內存隊列取出數據,統計取出數據%10的結果個數
* */
object QueueRdd {
def main(args: Array[String]) {
val conf = new SparkConf().setMaster("local[*]").setAppName("QueueRdd")
val ssc = new StreamingContext(conf, Seconds(1))
// Create the queue through which RDDs can be pushed to
// a QueueInputDStream
//創建RDD隊列
val rddQueue = new mutable.SynchronizedQueue[RDD[Int]]()
// Create the QueueInputDStream and use it do some processing
// 創建QueueInputDStream
val inputStream = ssc.queueStream(rddQueue)
//處理隊列中的RDD數據
val mappedStream = inputStream.map(x => (x % 10, 1))
val reducedStream = mappedStream.reduceByKey(_ + _)
//打印結果
reducedStream.print()
//啓動計算
ssc.start()
// Create and push some RDDs into
for (i <- 1 to 30) {
rddQueue += ssc.sparkContext.makeRDD(1 to 300, 10)
Thread.sleep(2000)
//通過程序停止StreamingContext的運行
//ssc.stop()
}
ssc.awaitTermination()
}
}
2. 自定義數據源
用法及說明
需要繼承Receiver,並實現onStart、onStop方法來自定義數據源採集。
案例實操
需求
:自定義數據源,實現監控某個端口號,獲取該端口號內容。
代碼:
package com.atguigu.streaming
import java.io.{BufferedReader, InputStreamReader}
import java.net.Socket
import java.nio.charset.StandardCharsets
import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.receiver.Receiver
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* 模仿 package org.apache.spark.streaming.dstream.SocketReceiver
*/
// String就是接收數據的類型
class CustomReceiver(host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_AND_DISK_2) {
override def onStart(): Unit = {
// Start the thread that receives data over a connection
new Thread("Socket Receiver") {
override def run() {
receive()
}
}.start()
}
override def onStop(): Unit = {
// 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) {
// socket 接受數據 需要一個結束的信號
// 內部的函數,將數據存儲下倆
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)
}
}
}
object CustomReceiver {
def main(args: Array[String]) {
val conf = new SparkConf().setMaster("local[*]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(conf, Seconds(1))
// Create a DStream that will connect to hostname:port, like localhost:9999,自定義數據源的操作
val lines = ssc.receiverStream(new CustomReceiver("localhost", 9999))
// Split each line into words
val words = lines.flatMap(_.split(" "))
//import org.apache.spark.streaming.StreamingContext._ // not necessary since Spark 1.3
// Count each word in each batch
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.reduceByKey(_ + _)
// Print the first ten elements of each RDD generated in this DStream to the console
wordCounts.print()
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
//ssc.stop()
}
}
總結:依葫蘆畫瓢,繼承必須的類,重寫方法實現自己業務邏輯。
3. Kafka數據源(面試開發重點)
用法及說明
在工程中需要引入Maven工件spark-streaming-kafka-0-8_2.11
來使用它。包內提供的 KafkaUtils
對象可以在StreamingContext
和JavaStreamingContext
中以你的Kafka消息創建出 DStream。
兩個核心類
:KafkaUtils、KafkaCluster
案例實操
需求
:通過SparkStreaming從Kafka讀取數據,並將讀取過來的數據做簡單計算(WordCount),最終打印到控制檯。
簡單版
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sparkStreaming</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sparkstreaming_windowWordCount</artifactId>
<dependencies>
<!-- 用來連接Kafka的工具類 -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.10.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-8_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
</dependencies>
<build>
<finalName>statefulwordcount</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.atguigu.streaming.WorldCount</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
代碼
package com.atguigu.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
object WorldCount {
def main(args: Array[String]) {
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
// 間隔採集週期, 若干半生類
val ssc = new StreamingContext(conf, Seconds(2))
ssc.checkpoint("./checkpoint")
// Create a DStream that will connect to hostname:port, like localhost:9999, 一行一行的接受數據
val lines: ReceiverInputDStream[String] = ssc.socketTextStream("localhost", 9000)
// 這裏主要是創建好 sparkStream如何跟消費Kafka的 邏輯代碼
val value: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, "zpIp:2181", "sowhatGroup", Map("sowhat" -> 3))
// Split each line into words 接受到的Kafka數據都是KV對,一般情況下不傳K而已,
val words: DStream[String] = value.flatMap(_._2.split(" "))
//import org.apache.spark.streaming.StreamingContext._ // not necessary since Spark 1.3
// Count each word in each batch
val pairs: DStream[(String, Int)] = words.map(word => (word, 1))
val wordCounts: DStream[(String, Int)] = pairs.reduceByKey(_ + _)
wordCounts.print()
// 啓動採集器
ssc.start()
// Driver等待採集器執行
ssc.awaitTermination()
//ssc.stop() // 把採集流停止 一般不用 因爲是不間斷的
}
}
連接池版
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>sparkstreaming_kafka</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 提供對象連接池的一種方式 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
<!-- 用來連接Kafka的工具類 -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.10.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
</dependencies>
<build>
<finalName>kafkastreaming</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.atguigu.streaming.KafkaStreaming</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
KafkaStreaming:
package com.atguigu.streaming
import org.apache.commons.pool2.impl.{GenericObjectPool, GenericObjectPoolConfig}
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, HasOffsetRanges, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}
//單例對象
object createKafkaProducerPool {
//用於返回真正的對象池GenericObjectPool
def apply(brokerList: String, topic: String): GenericObjectPool[KafkaProducerProxy] = {
val producerFactory = new BaseKafkaProducerFactory(brokerList, defaultTopic = Option(topic))
val pooledProducerFactory = new PooledKafkaProducerAppFactory(producerFactory)
//指定了你的kafka對象池的大小
val poolConfig = {
val c = new GenericObjectPoolConfig
val maxNumProducers = 10
c.setMaxTotal(maxNumProducers)
c.setMaxIdle(maxNumProducers)
c
}
//返回一個對象池
new GenericObjectPool[KafkaProducerProxy](pooledProducerFactory, poolConfig)
}
}
object KafkaStreaming {
def main(args: Array[String]) {
//設置sparkconf
val conf = new SparkConf().setMaster("local[4]").setAppName("NetworkWordCount")
//新建了streamingContext
val ssc = new StreamingContext(conf, Seconds(1))
//kafka的地址
val brobrokers = "192.168.56.150:9092,192.168.56.151:9092,192.168.56.152:9092"
//kafka的隊列名稱
val sourcetopic = "source1";
//kafka的隊列名稱
val targettopic = "target1";
//創建消費者組名
var group = "con-consumer-group"
//kafka消費者配置
val kafkaParam = Map(
"bootstrap.servers" -> brobrokers, //用於初始化鏈接到集羣的地址
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
//用於標識這個消費者屬於哪個消費團體
"group.id" -> group,
//如果沒有初始化偏移量或者當前的偏移量不存在任何服務器上,可以使用這個配置屬性
//可以使用這個配置,latest自動重置偏移量爲最新的偏移量
"auto.offset.reset" -> "latest",
//如果是true,則這個消費者的偏移量會在後臺自動提交
"enable.auto.commit" -> (false: java.lang.Boolean),
//ConsumerConfig.GROUP_ID_CONFIG
);
//創建DStream,返回接收到的輸入數據
val stream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](ssc, LocationStrategies.PreferConsistent, ConsumerStrategies.Subscribe[String, String](Array(sourcetopic), kafkaParam))
//每一個stream都是一個ConsumerRecord
stream.map(s => ("id:" + s.key(), ">>>>:" + s.value())).foreachRDD(rdd => {
//對於RDD的每一個分區執行一個操作
rdd.foreachPartition(partitionOfRecords => {
// kafka連接池。
val pool = createKafkaProducerPool(brobrokers, targettopic)
//從連接池裏面取出了一個Kafka的連接
val p = pool.borrowObject()
//發送當前分區裏面每一個數據
partitionOfRecords.foreach { message => System.out.println(message._2); p.send(message._2, Option(targettopic)) }
// 使用完了需要將kafka還回去
pool.returnObject(p)
})
//更新offset信息
val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
})
ssc.start()
ssc.awaitTermination()
}
}
第4章 DStream轉換
DStream上的操作與RDD的類似,分爲Transformations
(轉換)和Output Operations
(輸出)兩種,此外轉換操作中還有一些比較特殊的原語,如:updateStateByKey()、transform()以及各種Window相關的原語。
1. 無狀態轉化操作
無狀態轉化操作就是把簡單的RDD轉化操作應用到每個批次
上,也就是轉化DStream中的每一個RDD。部分無狀態轉化操作列在了下表中。注意,針對鍵值對的DStream轉化操作(比如 reduceByKey())要添加import StreamingContext._
才能在Scala中使用。
需要記住的是,儘管這些函數看起來像作用在整個流上一樣,但事實上每個DStream在內部是由許多RDD(批次)組成,且無狀態轉化操作是分別應用到每個RDD上
的。
例如:reduceByKey()會歸約每個時間區間中的數據,但不會歸約不同區間之間的數據。
package com.atguigu.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* Created by wuyufei on 06/09/2017.
*/
object WorldCount {
def main(args: Array[String]) {
val conf:SparkConf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc:StreamingContext = new StreamingContext(conf, Seconds(1))
ssc.checkpoint("./checkpoint")
// Create a DStream that will connect to hostname:port, like localhost:9999
val lines = ssc.socketTextStream("localhost", 9999)
// Split each line into words
val words = lines.flatMap(_.split(" "))
// Count each word in each batch
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.reduceByKey(_ + _)
// 注意此處是無狀態的,每次只處理對應的時間間隔數據!!
// Print the first ten elements of each RDD generated in this DStream to the console
wordCounts.print()
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
//ssc.stop()
}
}
2. 有狀態轉化操作
1. UpdateStateByKey
UpdateStateByKey原語用於記錄歷史
記錄,有時,我們需要在DStream中跨批次
維護狀態(例如流計算中累加wordcount)。針對這種情況,updateStateByKey()
爲我們提供了對一個狀態變量的訪問,用於鍵值對形式的DStream。給定一個由(鍵,事件)對構成的 DStream,並傳遞一個指定如何根據新的事件更新每個鍵對應狀態的函數,它可以構建出一個新的 DStream,其內部數據爲(鍵,狀態) 對。
updateStateByKey() 的結果會是一個新的DStream
,其內部的RDD 序列是由每個時間區間對應的(鍵,狀態)對組成的。
updateStateByKey操作使得我們可以在用新信息進行更新時保持任意的狀態。爲使用這個功能,需要做下面兩步:
- 定義狀態,狀態可以是一個任意的數據類型。
- 定義狀態更新函數,用此函數闡明如何使用之前的狀態和來自輸入流的新值對狀態進行更新。
使用updateStateByKey
需要對檢查點目錄進行配置,會使用檢查點來保存狀態。 - 整體過程有點類似 SparkSQL中的 自定義函數求均值,其中中間和跟個數的存儲,不過這裏是存儲早checkpoint中,保存到硬盤。
更新版的 wordcount
package com.atguigu.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* Created by wuyufei on 06/09/2017.
*/
object WorldCount {
def main(args: Array[String]) {
val conf:SparkConf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc:StreamingContext = new StreamingContext(conf, Seconds(1))
ssc.checkpoint("./checkpoint")
// Create a DStream that will connect to hostname:port, like localhost:9999
val lines = ssc.socketTextStream("localhost", 9999)
// Split each line into words
val words = lines.flatMap(_.split(" "))
// Count each word in each batch
val pairs = words.map(word => (word, 1))
// 上面給每個數據提供了 個數1, 然後 根據key 分組後就有個Seq[Int]的數據,然後不同的時間段數據需要累計 需要一箇中間緩衝變量 buffer .
val wordCounts: DStream[(String, Int)] = pairs.updateStateByKey {
case (seq, buffer) => {
val sum: Int = buffer.getOrElse(0) + seq.sum
Option(sum)
}
}
// Print the first ten elements of each RDD generated in this DStream to the console
wordCounts.print()
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
//ssc.stop()
}
}
2. WindowOperations
Window Operations可以設置窗口的大小
和滑動窗口的間隔
來動態的獲取當前Steaming的允許狀態。所有基於窗口的操作都需要兩個參數,分別爲窗口時長以及滑動步長。
- 窗口時長:計算內容的時間範圍;
- 滑動步長:隔多久觸發一次計算。
注意
:這兩者都必須爲批次大小的整數倍。
如下圖所示WordCount案例:窗口大小爲批次的2倍,滑動步等於批次大小。
WordCount第三版:3秒一個批次,窗口12秒,滑步6秒。
package com.atguigu.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
object WorldCount {
def main(args: Array[String]) {
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(conf, Seconds(3))
ssc.checkpoint("./ck")
// Create a DStream that will connect to hostname:port, like localhost:9999
val lines = ssc.socketTextStream("hadoop102", 9999)
// Split each line into words
val words = lines.flatMap(_.split(" "))
// Count each word in each batch
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.reduceByKeyAndWindow((a:Int,b:Int) => (a + b),Seconds(12), Seconds(6))
// Print the first ten elements of each RDD generated in this DStream to the console
wordCounts.print()
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
}
}
關於Window的操作還有如下方法:
- window(windowLength, slideInterval):
基於對源DStream窗化的批次進行計算返回一個新的Dstream;
- countByWindow(windowLength, slideInterval):
返回一個滑動窗口計數流中的元素個數;
- reduceByWindow(func, windowLength, slideInterval):
通過使用自定義函數整合滑動區間流元素來創建一個新的單元素流;
- reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]):
當在一個(K,V)對的DStream上調用此函數,會返回一個新(K,V)對的DStream,此處通過對滑動窗口中批次數據使用reduce函數來整合每個key的value值。
- reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]):
這個函數是上述函數的變化版本,每個窗口的reduce值都是通過用前一個窗的reduce值來遞增計算。通過reduce進入到滑動窗口數據並”反向reduce”離開窗口的舊數據來實現這個操作。一個例子是隨着窗口滑動對keys的“加”“減”計數。通過前邊介紹可以想到,這個函數只適用於”可逆的reduce函數”,也就是這些reduce函數有相應的”反reduce”函數(以參數invFunc形式傳入)。如前述函數,reduce任務的數量通過可選參數來配置。
al ipDStream = accessLogsDStream.map(logEntry => (logEntry.getIpAddress(), 1))
val ipCountDStream = ipDStream.reduceByKeyAndWindow(
{(x, y) => x + y},
{(x, y) => x - y},
Seconds(30),
Seconds(10))
//加上新進入窗口的批次中的元素 //移除離開窗口的老批次中的元素 //窗口時長// 滑動步長
countByWindow()
和countByValueAndWindow()
作爲對數據進行計數操作的簡寫。countByWindow()
返回一個表示每個窗口中元素個數的DStream,而countByValueAndWindow()
返回的DStream
則包含窗口中每個值的個數。
val ipDStream = accessLogsDStream.map{entry => entry.getIpAddress()}
val ipAddressRequestCount = ipDStream.countByValueAndWindow(Seconds(30), Seconds(10))
val requestCount = accessLogsDStream.countByWindow(Seconds(30), Seconds(10))
3. 其他重要操作
1. Transform
Transform原語允許DStream上執行任意的RDD-to-RDD函數,即使這些函數並沒有在DStream的API中暴露處理,通過改函數可以方便的擴展Spark API,改函數每一批次調用一次,其實也就是DStream中的RDD應用轉換, 比如下面的例子,單詞統計要過濾掉spam信息
val spamRDD = ssc.sparkContext.newAPIHadoopRDD()
val cleanDStream = wordCounts.transform{
rdd=> {rdd.join(spamInfoRDD).filter()}
}
---
關鍵是理解 執行次數的不同!
// 轉換
// TODO 代碼 Driver 執行 1次
val a = 1
socketLineDStream.map{
case x =>{
// TODO executor執行 n次
val a = 1 // 執行N 次
x
}
}
// TODO Driver中執行一次
socketLineDStream.transform{
case rdd=>{
// TODO Driver 執行 執行 週期次
rdd.map{
case x=> {
// todo Executor 執行 N次
x
}
}
}
}
2. Join
連接操作(leftOuterJoin、rightOutJoin、fullOuterJoin也可以),可以連接Stream-Stream,windows-Stream to windows-stream
第5章 DStream輸出
輸出操作指定了對流數據經轉化操作得到的數據所要執行的操作(例如把結果推入外部數據庫或輸出到屏幕上)。與RDD中的惰性求值類似,如果一個DStream及其派生出的DStream都沒有被執行輸出操作,那麼這些DStream就都不會被求值。如果StreamingContext中沒有設定輸出操作,整個context就都不會啓動。
輸出操作如下:
- print():
在運行流程序的驅動結點上打印DStream中每一批次數據的最開始10個元素。這用於開發和調試。在Python API中,同樣的操作叫print()。
- saveAsTextFiles(prefix, [suffix]):
以text文件形式存儲這個DStream的內容。每一批次的存儲文件名基於參數中的prefix和suffix。”prefix-Time_IN_MS[.suffix]”。
- saveAsObjectFiles(prefix, [suffix]):
以Java對象序列化的方式將Stream中的數據保存爲 SequenceFiles . 每一批次的存儲文件名基於參數中的爲"prefix-TIME_IN_MS[.suffix]". Python中目前不可用。
- saveAsHadoopFiles(prefix, [suffix]):
將Stream中的數據保存爲 Hadoop files. 每一批次的存儲文件名基於參數中的爲"prefix-TIME_IN_MS[.suffix]"。Python API 中目前不可用。
- foreachRDD(func):
這是最通用的輸出操作,即將函數 func 用於產生於 stream的每一個RDD。其中參數傳入的函數func應該實現將每一個RDD中數據推送到外部系統,如將RDD存入文件或者通過網絡將其寫入數據庫。通用的輸出操作foreachRDD(),它用來對DStream中的RDD運行任意計算。這和transform() 有些類似,都可以讓我們訪問任意RDD。在foreachRDD()中,可以重用我們在Spark中實現的所有行動操作。比如,常見的用例之一是把數據寫到諸如MySQL的外部數據庫中。
注意
:
6. 數據庫的連接不能寫在driver層面(因爲鏈接無法序列化)
7. 如果寫在foreach則每個RDD中的每一條數據都創建,得不償失
8. 增加foreachPartition,在分區創建(獲取)