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 <!-- spark streaming kafka --> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-kafka-0-8_2.11</artifactId> <version>${spark.version}</version> </dependency> <!-- kafka --> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>${kafka.version}</version> </dependency> 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