DStream上的操作与RDD的类似,分为Transformations(转换)和Output Operations(输出)两种,此外转换操作中还有一些比较特殊的原语,如:updateStateByKey()、transform()以及各种Window相关的算子。
- 无状态转化操作就是把简单的RDD转化操作应用到每个批次上,也就是转化DStream中的每一个RDD。部分无状态转化操作列在了下表中。
- 尽管这些函数看起来像作用在整个流上一样,但事实上每个DStream在内部是由许多RDD(批次)组成,且无状态转化操作是分别应用到每个RDD上的。
- 例如:reduceByKey()会归约每个时间区间中的数据,但不会归约不同区间之间的数据。
- 简而言之是每个批次进行单独得WordCount,批次与批次之间没有联系,不是真正得WordCount,只是批次内得WordCount
- Transform允许DStream上执行任意的RDD-to-RDD函数。即使这些函数并没有在DStream的API中暴露出来,通过该函数可以方便的扩展Spark API。
- 该函数每一批次调度一次。其实也就是对DStream中的RDD应用转换。
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming_transform")
val ssc = new StreamingContext(conf,Seconds(3))
val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)
// println(Thread.currentThread().getName) // 一次 在Driver端
// 将Dstream得操作直接转换为RDD进行操作
val wordToCountDstrem: DStream[(String, Int)] = lineDStream.transform {
// println(Thread.currentThread().getName) // 每次批次一次 在Driver端
rdd => {
rdd.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
// println(Thread.currentThread().getName) // 每次批次元素个数几个 在exceator端
}
}
wordToCountDstrem.print()
ssc.start()
ssc.awaitTermination()
}
两个流之间的join需要两个流的批次大小一致,这样才能做到同时触发计算。计算过程就是对当前批次的两个流中各自的RDD进行join,与两个RDD的join效果相同。
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming_transform")
val ssc = new StreamingContext(conf,Seconds(3))
val lineDStream1: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)
val lineDStream2: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 8888)
// 转换为kv节奏
val wordToOneDStrem: DStream[(String, Int)] = lineDStream1.map((_,1))
val wordToADStrem: DStream[(String, Int)] = lineDStream2.map((_,"a"))
// 流得Join
val joinDStream: DStream[(String, (Int, String))] = wordToOneDStrem.join(wordToADStrem)
joinDStream.print()
ssc.start()
ssc.awaitTermination()
}
程序挂掉后不能接着续传,只能从头再来,若想实现续传,需要读取上一次结果
updateStateByKey用于记录历史记录,有时我们需要在DStream中跨批次维护状态(例如流计算中累加WordCount)。针对这种情况,updateStateByKey()为我们提供了对一个状态变量的访问,用于键值对形式的DStream。给定一个由(键,事件)对构成的DStream,并传递一个指定如何根据新的事件更新每个键对应状态的函数,它可以构建出一个新的DStream,其内部数据为(键,状态) 对。
updateStateByKey()的结果会是一个新的DStream,其内部的RDD序列是由每个时间区间对应的(键,状态)对组成的。
updateStateByKey操作使得我们可以在用新信息进行更新时保持任意的状态。为使用这个功能,需要做下面两步:
- 定义状态,状态可以是一个任意的数据类型。
- 定义状态更新函数,用此函数阐明如何使用之前的状态和来自输入流的新值对状态进行更新。
更新版的WordCount
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming_transform")
val ssc = new StreamingContext(conf,Seconds(3))
// 存储地方
ssc.checkpoint("./updata")
val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)
val wordToCount: DStream[(String, Int)] = lineDStream.flatMap(_.split(" ")).map((_,1))
// updateFunc(Seq[V],Option[S]) => Option[S]
// Seq -> 当前得数据集 ; Option -> 以前得数据集
val updataFunc= (seq:Seq[Int],state:Option[Int]) =>{
val sum: Int = seq.sum
val lastSum: Int = state.getOrElse(0)
Some(sum + lastSum)
}
val wordToCountDStream: DStream[(String, Int)] = wordToCount.updateStateByKey(updataFunc)
wordToCountDStream.print()
ssc.start()
ssc.awaitTermination()
}
Window Operations可以设置窗口的大小和滑动窗口的间隔来动态的获取当前Steaming的允许状态。所有基于窗口的操作都需要两个参数,分别为窗口时长以及滑动步长。
(1)窗口时长:计算内容的时间范围;
(2)滑动步长:隔多久触发一次计算。
这两者都必须为批次大小的整数倍。
下图为:窗口大小为批次的2倍,滑动步等于批次大小。
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming_windowOperations")
val ssc = new StreamingContext(conf,Seconds(3))
val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)
// 开窗 计算时间和批次时间默认一致,
// 批次时间为3 则计算时间(滑动步长)也为3 意思为每批次都处理一次
// (窗口大小)9秒除以批次时间3 => 为3 则每次计算处理三个批次
/* val windowDStream: DStream[String] = lineDStream.window(Seconds(9))
windowDStream.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()*/
// 计算得过程中 进行开窗
lineDStream.flatMap(_.split(" "))
.map((_,1))
.reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(9),Seconds(6))
ssc.start()
ssc.awaitTermination()
}
(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值。
(5)reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]): 这个函数是上述函数的变化版本,每个窗口的reduce值都是通过用前一个窗的reduce值来递增计算。通过reduce进入到滑动窗口数据并”反向reduce”离开窗口的旧数据来实现这个操作。
(6)countByWindow()和countByValueAndWindow()作为对数据进行计数操作的简写。countByWindow()返回一个表示每个窗口中元素个数的DStream,而countByValueAndWindow()返回的DStream则包含窗口中每个值的个数。