Spark复习 Day04:SparkStreaming
1. SparkStreaming版的WordCount
---------------------------------
@Test
def TestStreaming(): Unit ={
val conf = new SparkConf().setAppName("sc").setMaster("local")
val streamingContext = new StreamingContext(conf, Seconds(3))
val scDStream: ReceiverInputDStream[String] = streamingContext.socketTextStream("localhost", 9988)
val res: DStream[(String, Int)] = scDStream.flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_ + _)
res.print()
// 开启streaming
streamingContext.start()
// 等待采集器结束而结束
streamingContext.awaitTermination()
}
2. SparkStreaming 数据源
--------------------------------------
- 文件数据源
1. streamingContext.textFileStream(dir)
2. 监控一个目录,产生新文件就读取
3. 文件需要是相同的数据格式
4. 文件一旦进入目录,就不能再次修改,修改也不会重新读取数据
- 在内存中序列化数据源
@Test
def TestQueueRDD(): Unit ={
val conf = new SparkConf().setMaster("local").setAppName("sc")
val streamingContext = new StreamingContext(conf,Seconds(3))
val queue: mutable.Queue[RDD[Int]] = mutable.Queue[RDD[Int]]()
val stream: InputDStream[Int] = streamingContext.queueStream(queue,oneAtATime = false)
val res: DStream[(Int, Int)] = stream.map((_,1)).reduceByKey(_ + _)
res.print()
streamingContext.start()
for(i <- 1 to 1000){
val rdd = streamingContext.sparkContext.makeRDD(1 to i * 2,10)
queue.enqueue(rdd)
Thread.sleep(2000)
}
streamingContext.awaitTermination()
- 自定义数据源
class MyReceiver extends Receiver[String](StorageLevel.MEMORY_AND_DISK) {
override def onStart(): Unit = {
}
override def onStop(): Unit = {
}
}
- kafka数据源
1. pom
org.apache.spark
spark-streaming-kafka-0-8_2.11
${spark.version}
org.apache.kafka
kafka-clients
${kafka.version}
2. KafkaUtils.scala
@Test
def TestKafka(): Unit ={
val conf = new SparkConf().setMaster("local").setAppName("sc")
val streamingContext = new StreamingContext(conf,Seconds(3))
val kafkaStream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(
ssc = streamingContext,
zkQuorum = "hadoop01:2181,hadoop02:2181,hadoop02:2181",
groupId = "groupId",
topics = Map("topic1" -> 3)
)
val res: DStream[String] = kafkaStream.flatMap(t => t._2.split("\\s+"))
res.print()
streamingContext.start()
streamingContext.awaitTermination()
}
3. DStream 的转换
---------------------------------------------
- 无状态操作
1. 无状态转化就是把简单的计算逻辑应用到批次的每个RDD上。只对当前批次的RDD有影响,对以前批次的RDD毫无关联
2. 主要有:
map
flatMap
filter
repartition
reduceByKey : 只会计算当前批次RDD的数据,不会跨批次
groupByKey
...
- 有状态操作
1. 操作会保存以前批次的状态[计算结果],与当前批次进行交互计算
2. 可以保存到内存或者文件系统
3. 主要有:
- UpdateStateByKey
- Window Operations
4. 有状态算子 UpdateStateByKey
----------------------------------------------------
- 用于跨批次维护状态,例如流计算中累加单词统计
- UpdateStateByKey的结果是一个新的DStream,其内部的RDD序列是由每个批次计算结果[k,v]组成的
- 使用:
1. 定义状态,状态可以是任意的数据类型、用来保存以前批次的计算结果
2. 定义状态更新函数: 以前的状态 + 输入流的数据 => 新的状态
- UpdateStateByKey需要对检查点目录进行配置,算法内部需要进行检查点保存计算的结果[状态]
- 例:
@Test
def testKafka(): Unit ={
val conf = new SparkConf().setMaster("local").setAppName("sc")
val streamingContext = new StreamingContext(conf,Seconds(3))
val kafkaStream: DStream[(String, Int)] = KafkaUtils.createStream(
ssc = streamingContext,
zkQuorum = "hadoop01:2181,hadoop02:2181,hadoop02:2181",
groupId = "groupId",
topics = Map("topic1" -> 3)
).map(_._2).flatMap(_.split("\\s+")).map((_,1))
// updateStateByKey使用前,需要指定检查点
streamingContext.sparkContext.setCheckpointDir("cp")
// 简单的统计kafka单词出现的次数
// updateStateByKey的ByKey说明了此算子肯定会先根据key进行分组,相同的key进入一组,所以,算子里面只操作value即可,然后更新key的value
// 我缓存的状态 其实就是一个Option[Int]的值,就是updateFunc方法的返回值
// 首先,第一次进来,seq[Int]里面是第一批进来,单词==key 的单词的出现的次数 seq[1,1,1,1] = 本批次单词key出现了4次
// 然后去buffer里面取缓存的状态,因为buffer里面是没有值的,所以取出默认值0
// 然后执行你定义的逻辑 sum(seq) + buffer ==> 结果为4 ==> 截止至当前批次,单词统计为4,并且更新到缓存buffer里面
// 下一个批次key单词出现了seq[1,1] 传入,取出buffer里面的4,执行你的逻辑,计算结果 =6, 更新缓冲区
// 依次循环,去更新缓冲区
// 然后每隔 new StreamingContext(conf,Seconds(3)) 限定的时间,去打印一下数据
val res: DStream[(String, Int)] = kafkaStream.updateStateByKey({
// updateFunc
case (seq: Seq[Int], buffer: Option[Any]) =>
// seq中缓存的都是以前的单词的单词数量,怎么操作你说了算
Option(buffer.getOrElse(0) + seq.sum)
})
res.print()
streamingContext.start()
streamingContext.awaitTermination()
}
5. 有状态算子 Window Operations
-----------------------------------------------------------
- Window Operations 允许你设置一个窗口的大小和滑动的间隔,来动态计算和获取缓冲区的状态
- 首先,你的streamingContext有一个采集周期例如3s,你的窗口可以设置成10分钟,这样就会将最近200次采集的结果整合到一起
- 然后,设置成每1分钟滑动一次,那么你的窗口就是一分钟统计一次实时的10分钟内的数据结果
- 常用操作:
DStream.window(duration, slider)
DStream.countByWindow(duration, slider)
DStream.reduceByWindow(func, duration, slider)
DStream.reduceByKeyAndWindow(func, duration, slider, [numTasks])
- 例:
@Test
def testWindowOperations(): Unit ={
val conf = new SparkConf().setMaster("local").setAppName("sc")
val streamingContext = new StreamingContext(conf,Seconds(3))
val kafkaStream: DStream[(String, String)] = KafkaUtils.createStream(
ssc = streamingContext,
zkQuorum = "hadoop01:2181,hadoop02:2181,hadoop02:2181",
groupId = "groupId",
topics = Map("topic1" -> 3)
)
// 窗口大小和滑动都是采集周期的整数倍
// 这样就每1分钟,统计最近10分钟的数据,然后计算一个结果
val windowDStream: DStream[(String, String)] = kafkaStream.window(Minutes(10), Minutes(1))
val res: DStream[(String, Int)] = windowDStream.map(_._2).flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_ + _)
res.print()
streamingContext.start()
streamingContext.awaitTermination()
}
6. Transform 算子
--------------------------------------------------
// 转换,就是封装一些RDD的操作,作为一个transform
// 但是,如果涉及到需要动态更新的逻辑,这个就起到很大的作用了
kafkaStream.transform(x => {
// TODO 此位置写的代码,都是重复执行的,同步于streamingContext的采集频率
// 这里可以处理你的动态更新逻辑
// 可以放你的需要每批次更改的变量等等
// 比如黑名单机制,你每次读取一个批次的数据,都要实时更新,读取最新的黑名单机制
x.map(cc => cc._1)
})
7. foreachRDD
-------------------------------------------------
- 一个streamingContext的采集周期会形成一个RDD
- 一个DStream 会有一个或者多个RDD
- DStream.foreachRDD(rdd => ....) // 能够取到每一个RDD