目录
Transformations on DStreams
DStream支持普通Spark RDD上可用的许多转换。一些常见的方法如下:
Transformation | Meaning |
map(func) | 通过将源DStream的每个元素传递给函数func来返回新的DStream |
flatMap(func) | 在map变形的基础上,对DStream进行降维 |
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函数汇总每个键的值 |
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中,通过在键的先前状态和键的新值上应用给定的函数来更新每个键的状态。这可用于维护每个键的任意状态数据 |
下面对部分opreation进行分析:
UpdateStateByKey
- 为Spark Streaming中每一个Key维护一份state状态,state类型可以是任意类型的, 可以是一个自定义的对象,那么更新函数也可以是自定义的;
- 通过更新函数对该key的状态不断更新,对于每个新的batch而言,Spark Streaming会在使用updateStateByKey的时候为已经存在的key进行state的状态更新
e.g.
maven denpency:
<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-core -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-sql -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-streaming -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.1.0</version>
</dependency>
code
package cn.wsj.mysparkstreaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
object NcWordCount {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setAppName(this.getClass.getName)
.setMaster("local[4]")
val ssc: StreamingContext = new StreamingContext(conf, Seconds(5))
ssc.checkpoint("e:/ck")
val line: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.237.160", 1234)
line.flatMap(_.split(" "))
.map((_, 1))
.updateStateByKey((currVal:Seq[Int],preState:Option[Int])=>{
val newVal = currVal.sum
val oldVal = preState.getOrElse(0)
Some(newVal+oldVal)
})
.reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
}
}
打开一个netcat
# 安装网络工具netcat
ymu -y install nc
# 打开一个socket ,这里我开启的是1234端口
nc -lk 1234
Test Result:
在spark客户端可以看到如下变化,每段时间都会显示每个单词的词频统计结果:
-------------------------------------------
Time: 1617245130000 ms
-------------------------------------------
(java,1)
-------------------------------------------
Time: 1617245135000 ms
-------------------------------------------
(java,2)
-------------------------------------------
Time: 1617245140000 ms
-------------------------------------------
(hadoop,1)
(java,4)
-------------------------------------------
Time: 1617245145000 ms
-------------------------------------------
(hadoop,1)
(scala,1)
(java,5)
-------------------------------------------
Time: 1617245150000 ms
-------------------------------------------
(hive,1)
(hadoop,1)
(scala,1)
(java,6)
Process finished with exit code -1
Transform
transform操作允许将任意RDD到RDD函数应用于DStream。 它可用于应用任何未在DStream API中公开的RDD操作。
e.g.
object MyTransform {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[4]")
.setAppName(this.getClass.getName)
val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
ssc.checkpoint("e:/ck")
val ds = ssc.socketTextStream("192.168.237.160", 9000)
val wd = ds.flatMap(_.split(" ")).map((_,1))
val ww = wd.reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(9), Seconds(3))
val jj = ww.transform(x => {
val tp = x.map(tp => (tp._2, tp._1))
val pp = tp.sortByKey(false)
pp.take(3).map(t => (t._2, t._1)).foreach(e => print(e))
x
})
jj.print()
ssc.start()
ssc.awaitTermination()
}
}
Window
Spark Streaming还提供了窗口计算,可让您在数据的滑动窗口上应用转换。下图说明了此滑动窗口:
如该图所示,每当窗口滑动在源DSTREAM,落入窗口内的源RDDS被组合及操作以产生RDDS的窗DSTREAM。在这种特定情况下,该操作将应用于数据的最后3个时间单位,并以2个时间单位滑动。这表明任何窗口操作都需要指定两个参数。
- window length(窗口宽度) - The duration of the window (3 in the figure)
- sliding interval(华东间隔) - The interval at which the window operation is performed (2 in the figure)
这两个参数必须是源DStream的批处理间隔的倍数(上图为1)
e.g.
object MyWindow {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[4]")
.setAppName(this.getClass.getName)
val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
ssc.checkpoint("e:/ck")
val ds = ssc.socketTextStream("192.168.237.160", 9000)
val si: DStream[(String, Int)] = ds.flatMap(_.split(" ")).map((_, 1))
.reduceByKeyAndWindow((a: Int, b: Int) => (a + b), Seconds(9), Seconds(3))
si.print()
ssc.start()
ssc.awaitTermination()
}
}
常见的窗口操作如下。所有这些操作都采用上述两个参数-windowLength和slideInterval。
Transformation | Meaning |
window(windowLength, slideInterval) | 返回一个新的DStream,该DStream是基于源DStream的窗口批处理计算的 |
countByWindow(windowLength, slideInterval) | 返回流中元素的滑动窗口计数 |
reduceByWindow(func, windowLength, slideInterval) | 返回一个新的单元素流,该流是通过使用func在一个滑动间隔内聚合流中的元素而创建的。该函数应该是关联的和可交换的,以便可以并行正确地计算它。 |
reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]) | 在(K,V)对的DStream上调用时,返回一个新的(K,V)对的DStream,其中每个键的值使用给定的reduce函数func 在滑动窗口中的批处理上聚合。 |
reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]) | 上述方法的一种更有效的版本,reduceByKeyAndWindow()其中,使用前一个窗口的减少值递增地计算每个窗口的减少值。这是通过减少进入滑动窗口的新数据并“逆向减少”离开窗口的旧数据来完成的。一个示例是在窗口滑动时“增加”和“减少”键的计数。但是,它仅适用于“可逆归约函数”,即具有相应的“逆归约”函数(作为参数invFunc)的归约函数。像in中一样reduceByKeyAndWindow,reduce任务的数量可以通过可选参数配置。请注意,必须启用检查点才能使用此操作。 |
countByValueAndWindow(windowLength, slideInterval, [numTasks]) | 在(K,V)对的DStream上调用时,返回新的(K,Long)对的DStream,其中每个键的值是其在滑动窗口内的频率。像in中一样 reduceByKeyAndWindow,reduce任务的数量可以通过可选参数配置。 |
Join
a. Stream-stream joins
val stream1: DStream[String, String] = ...
val stream2: DStream[String, String] = ...
val joinedStream = stream1.join(stream2)
在每个批处理间隔中,生成的RDDstream1将与生成的RDD合并在一起stream2。你也可以做leftOuterJoin,rightOuterJoin,fullOuterJoin。此外,在流的窗口上进行联接通常非常有用。
val windowedStream1 = stream1.window(Seconds(20))
val windowedStream2 = stream2.window(Minutes(1))
val joinedStream = windowedStream1.join(windowedStream2)
b. Stream-dataset joins
val dataset: RDD[String, String] = ...
val windowedStream = stream.window(Seconds(20))...
val joinedStream = windowedStream.transform { rdd => rdd.join(dataset)
实际上,还可以动态更改要加入的数据集。transform每个批次间隔都会评估提供给该函数的功能,因此将使用dataset参考所指向的当前数据集。
Output Operations
输出操作允许将DStream的数据推出到外部系统,例如数据库或文件系统。由于输出操作实际上允许转换后的数据被外部系统使用,因此它们会触发所有DStream转换的实际执行(类似于RDD的操作)。当前,定义了以下输出操作:
Output Operation | Meaning |
print() | 在运行流应用程序的驱动程序节点上,打印DStream中每批数据的前十个元素。这对于开发和调试很有用。 |
saveAsTextFiles(prefix, [suffix]) | prefix and suffix: "将此DStream的内容另存为文本文件。基于产生在每批间隔的文件名的前缀和 后缀: "prefix-TIME_IN_MS[.suffix]". |
saveAsObjectFiles(prefix, [suffix]) | 将此DStream的内容保存为SequenceFiles序列化Java对象的内容。基于产生在每批间隔的文件名的前缀和 后缀:"prefix-TIME_IN_MS[.suffix]" |
saveAsHadoopFiles(prefix, [suffix]) | 将此DStream的内容另存为Hadoop文件。基于产生在每批间隔的文件名的前缀和后缀:"prefix-TIME_IN_MS[.suffix]" |
foreachRDD(func) | 最通用的输出运算符,将函数func应用于从流生成的每个RDD。此功能应将每个RDD中的数据推送到外部系统,例如将RDD保存到文件或通过网络将其写入数据库。请注意,函数func在运行流应用程序的驱动程序进程中执行,并且通常在其中具有RDD操作,这将强制计算流RDD。 |
Other points
- DStream由输出操作延迟执行,就像RDD由RDD操作延迟执行一样。具体来说,DStream输出操作内部的RDD动作会强制处理接收到的数据。因此,如果您的应用程序没有任何输出操作,或者dstream.foreachRDD()内部没有任何RDD操作,就不会执行任何输出操作。系统将仅接收数据并将其丢弃
- 默认情况下,一次执行输出操作。它们按照在应用程序中定义的顺序执行
DataFrame and SQL Operations
可以对流数据使用DataFrames和SQL操作。必须使用StreamingContext使用的SparkContext创建一个SparkSession。此外,必须这样做,以便可以在驱动程序故障时重新启动它。这是通过创建SparkSession的延迟实例化单例实例来完成的。在下面的示例中显示了这一点。它修改了前面的单词计数示例,以使用DataFrames和SQL生成单词计数。每个RDD都转换为DataFrame,注册为临时表,然后使用SQL查询。
e.g.
val words: DStream[String] = ...
words.foreachRDD { rdd =>
// 创建SparkSession
val spark = SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate()
import spark.implicits._
// 将rdd转为df
val wordsDataFrame = rdd.toDF("word")
// 创建一个视图
wordsDataFrame.createOrReplaceTempView("words")
// 使用sql进行查询
val wordCountsDataFrame =
spark.sql("select word, count(*) as totalCount from words group by word")
wordCountsDataFrame.show()
}
PS:如果有写错或者写的不好的地方,欢迎各位大佬在评论区留下宝贵的意见或者建议,敬上!如果这篇博客对您有帮助,希望您可以顺手帮我点个赞!不胜感谢!
原创作者:wsjslient |
参考来源:Spark Streaming官网 |