Spark的认识(四)
1、本文内容
-
1、掌握SparkStreaming底层原理
-
2、掌握Dstream常用操作
-
3、掌握SparkStreaming整合flume
-
4、掌握SparkStreaming整合kafka
2、SparkStreaming概述
2.1 什么是sparkStreaming
-
Spark Streaming makes it easy to build scalable fault-tolerant streaming applications.
-
SparkStreaming是可以非常容易的构建一个可扩展、具有容错机制的流式应用程序
2.2 sparkStreaming特性
-
1、易用性
-
可以像开发离线批处理一样去编写流式处理程序,还是可以使用java、scala、Python等不同的语言开发
-
-
2、容错性
-
sparkStreaming可以实现恰好一次语义(数据被处理且只被处理一次)
-
sparkStreaming可以实现在不需要额外代码的情况下,来实现丢失的job
-
-
3、可以融合到spark生态系统
-
sparkstreaming可以和离线批处理和交互式查询相结合
-
3、sparkStreaming原理
3.1 sparkStreaming计算原理
Spark Streaming 是基于spark的流式批处理引擎,其基本原理是把输入数据以某一时间间隔批量的处理,当批处理间隔缩短到秒级时,便可以用于处理实时数据流。
3.2 sparkStreaming计算流程
sparkStreaming是以某一个时间间隔的批处理,按照批处理的时间间隔进行划分,划分出了很多个短小的批处理作业,每一个段数据在这里就是一个Dstream,Dstream内部是由当前该批次的数据,内部的数据是通过rdd进行封装,也就是说Dstream中封装了rdd,rdd里面有很多个分区,分区里面才是真正的数据。 后期Dstream做大量的transformation操作,最终都转换成了对它内部的rdd也就是对应的transformation。
3.3 sparkStreaming容错性
Dstream中内部封装了rdd,这个时候后期对它做大量操作的时候,如果某个rdd的分区数据丢失了,可以通过rdd的血统进行重新计算恢复得到。 通过血统在进行重新计算恢复得到丢失的数据的时候需要一定条件:就是保证数据源端安全性 sc.textFile("/words.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect 恢复数据: 血统+原始的数据进行恢复
3.4 sparkStreaming的实时性
storm是来一条数据就处理一条,实时性比较高 sparkStreamning是以某一时间间隔处理,延迟比较高,实时性比较低 后期再处理一些实时的应用的场景,具体使用哪一种框架去实现,一定要结合当前这个业务需求: 比如说 公司领导对实时性要求比较高,从数据生成到看到结果数据需要非常快的时间,这个时候优先考虑storm框架 公司领导对实时性要求不是特别高,从数据生成到看到结果数据可以允许有一定的延迟,这个时候就可以考虑使用sparkStreaming框架。
4、DStream
4.1 什么是Dstream
Discretized Stream是Spark Streaming的基础抽象,代表持续性的数据流和经过各种Spark算子操作后的结果数据流。在内部实现上,DStream是一系列连续的RDD来表示。每个RDD含有一段时间间隔内的数据。
4.2 Dstream操作分类
-
1、transformation(转换)
-
可以把一个Dstream转换生成一个新的Dstream。它也是延迟加载,不会立即触发任务的运行
-
类似于rdd的中transformation
-
-
2、outputOperation(输出)
-
它会触发任务的真正运行
-
类似于rdd的中action
-
5、Dstream操作实战
-
引入依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming_2.11</artifactId> <version>2.1.3</version> </dependency>
5.1 通过sparkStreaming接受socket数据实现单词统计
-
1、代码开发
package demo.socket import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} //todo:利用sparkStreaming接受socket数据实现单词统计 object SparkStreamingSocket { def main(args: Array[String]): Unit = { //1、创建SparkConf对象 val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocket").setMaster("local[2]") //2、创建SparkContext对象 val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔 val ssc = new StreamingContext(sc,Seconds(5)) //4、接受socket数据 val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999) //5、切分每一行获取所有的单词 val words: DStream[String] = socketTextStream.flatMap(_.split(" ")) //6、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //7、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //8、打印 result.print() //9、开启流式计算 ssc.start() ssc.awaitTermination() } }
5.2 通过sparkStreaming接受socket数据实现所有批次单词统计的结果累加
-
1、代码开发
package demo.socket import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.{SparkConf, SparkContext} //todo:利用sparkStreaming接受socket数据实现所有批次单词统计结果累加 object SparkStreamingSocketTotal { //currentValues:表示在当前批次中相同的单词出现的所有的1(hadoop---->List(1,1,1,1)) //historyValues:表示每一个单词在之前所有批次中出现总次数 //Option类型:可以表示可能存在或者不存在的值 存在Some, 不存在None def updateFunc(currentValues:Seq[Int],historyValues:Option[Int]):Option[Int] = { val newValue: Int = currentValues.sum + historyValues.getOrElse(0) Some(newValue) } def main(args: Array[String]): Unit = { //1、创建SparkConf val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketTotal").setMaster("local[2]") //2、创建SparkContext val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext val ssc = new StreamingContext(sc,Seconds(5)) //设置checkpoint目录,主要的作用:用于保存之前批次每一个单词出现的总次数 ssc.checkpoint("./socket") //4、接受socket数据 val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999) //5、切分每一行获取所有的单词 val words: DStream[String] = socketTextStream.flatMap(_.split(" ")) //6、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //7、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.updateStateByKey(updateFunc) //8、打印 result.print() //9、开启流式计算 ssc.start() ssc.awaitTermination() } }
5.3 通过sparkStreaming接受socket数据,利用reduceByKeyAndWindow来实现单词统计
-
1、代码开发
package demo.socket import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} //todo:利用reduceByKeyAndWindow来实现单词统计 object SparkStreamingSocketWindow { def main(args: Array[String]): Unit = { //1、创建SparkConf对象 val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketWindow").setMaster("local[2]") //2、创建SparkContext对象 val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔 val ssc = new StreamingContext(sc,Seconds(5)) //4、接受socket数据 val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999) //5、切分每一行获取所有的单词 val words: DStream[String] = socketTextStream.flatMap(_.split(" ")) //6、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //7、相同单词出现的1累加 //需要3个参数 // reduceFunc: (V, V) => V, 它就是一个函数 // windowDuration: Duration, 窗口的长度 // slideDuration: Duration 窗口的滑动时间间隔,表示每隔多久计算一次 val result: DStream[(String, Int)] = wordAndOne.reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(5),Seconds(10)) //8、打印 result.print() //9、开启流式计算 ssc.start() ssc.awaitTermination() } }
5.4 通过sparkStreaming使用开窗函数来统计一定内的热门词汇
-
1.代码开发
package demo.socket import org.apache.spark.rdd.RDD import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.{SparkConf, SparkContext} //todo:利用开窗函数来实现一定时间内的热门词汇 object SparkStreamingSocketWindowHotWords { def main(args: Array[String]): Unit = { //1、创建SparkConf val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketWindowHotWords").setMaster("local[2]") //2、创建SparkContext val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext val ssc = new StreamingContext(sc,Seconds(5)) //4、接受socket数据 val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999) //5、切分每一行获取所有的单词 val words: DStream[String] = socketTextStream.flatMap(_.split(" ")) //6、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //7、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(10),Seconds(10)) //按照单词出现的次数降序排列 val sortDstream: DStream[(String, Int)] = result.transform(rdd => { //按照单词次数降序 val sortRDD: RDD[(String, Int)] = rdd.sortBy(_._2, false) //取出次数最多的前3位 val top3: Array[(String, Int)] = sortRDD.take(3) println("---------------top3--------------start") top3.foreach(println) println("---------------top3--------------end") sortRDD }) //8、打印 sortDstream.print() //9、开启流式计算 ssc.start() ssc.awaitTermination() } }
6、sparkStreaming整合flume
6.1 Poll拉模式整合
-
1、需要将spark-streaming-flume-sink_2.11-2.1.3.jar放入到flume的lib目录下,同时还需要把flume自带的scala依赖2.10改为2.11
-
2、修改flume配置文件
-
vim flume-poll-spark.conf
a1.sources = r1 a1.sinks = k1 a1.channels = c1 #source a1.sources.r1.channels = c1 a1.sources.r1.type = spooldir a1.sources.r1.spoolDir = /root/data a1.sources.r1.fileHeader = true #channel a1.channels.c1.type =memory a1.channels.c1.capacity = 20000 a1.channels.c1.transactionCapacity=5000 #sinks a1.sinks.k1.channel = c1 a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink a1.sinks.k1.hostname=node1 a1.sinks.k1.port = 8888 a1.sinks.k1.batchSize= 2000
-
-
3、引入依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-flume_2.11</artifactId> <version>2.1.3</version> </dependency>
-
4、代码开发
package demo.flume import java.net.InetSocketAddress import org.apache.spark.storage.StorageLevel import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent} //todo:利用sparkStreaming整合flume-------Poll拉模式整合 object SparkStreamingFlumePoll { def main(args: Array[String]): Unit = { //1、创建SparkConf对象 val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingFlumePoll").setMaster("local[2]") //2、创建SparkContext对象 val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔 val ssc = new StreamingContext(sc,Seconds(5)) //4、通过拉模式接受flume的数据 val pollingStream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createPollingStream(ssc,"node1",8888) //接受多台flume收集到的数据 // val addresses=List(new InetSocketAddress("node1",8888),new InetSocketAddress("node2",8888),new InetSocketAddress("node3",8888)) // val stream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createPollingStream(ssc,addresses,StorageLevel.MEMORY_AND_DISK_SER_2) // //5、获取flume中数据 flume中数据传输的最小单元是一个event:{"headers":xxxxx,"body":xxxxxx} //获取body中的数据 val data: DStream[String] = pollingStream.map(x=>new String(x.event.getBody.array())) //6、切分每一行获取所有的单词 val words: DStream[String] = data.flatMap(_.split(" ")) //7、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //8、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //9、打印 result.print() //10、开启流式计算 ssc.start() ssc.awaitTermination() } }
6.2 Push推模式整合
-
1、编写flume的配置文件
-
vim flume-push-spark.conf
#push mode a1.sources = r1 a1.sinks = k1 a1.channels = c1 #source a1.sources.r1.channels = c1 a1.sources.r1.type = spooldir a1.sources.r1.spoolDir = /root/data a1.sources.r1.fileHeader = true #channel a1.channels.c1.type =memory a1.channels.c1.capacity = 20000 a1.channels.c1.transactionCapacity=5000 #sinks a1.sinks.k1.channel = c1 a1.sinks.k1.type = avro #是sparkStreaming应用程序所在的ip地址和端口 a1.sinks.k1.hostname=192.168.25.48 a1.sinks.k1.port = 8888 a1.sinks.k1.batchSize= 2000
-
-
2、代码开发
package demo.flume import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent} //todo:sparkStreaming整合flume-------Push推模式 object SparkStreamingFlumePush { def main(args: Array[String]): Unit = { //1、创建SparkConf对象 val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingFlumePush").setMaster("local[2]") //2、创建SparkContext对象 val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔 val ssc = new StreamingContext(sc,Seconds(5)) //4、接受flume中的数据 val stream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createStream(ssc,"192.168.25.48",8888) //5、获取flume中数据 flume中数据传输的最小单元是一个event:{"headers":xxxxx,"body":xxxxxx} //获取body中的数据 val data: DStream[String] = stream.map(x=>new String(x.event.getBody.array())) //6、切分每一行获取所有的单词 val words: DStream[String] = data.flatMap(_.split(" ")) //7、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //8、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //9、打印 result.print() //10、开启流式计算 ssc.start() ssc.awaitTermination() } }
7、SparkStreaming整合kafka
-
引入依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-kafka-0-8_2.11</artifactId> <version>2.1.3</version> </dependency>
7.1 KafkaUtils.createStream
-
1、代码开发
package demo.kafka import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.kafka.KafkaUtils import scala.collection.immutable //todo:sparkStreaming整合kafka -------基于receiver接收器,使用了kafka高层次消费者api(消息的偏移量由zk去维护) object SparkStreamingKafkaReceiver { def main(args: Array[String]): Unit = { //1、创建SparkConf对象 val sparkConf: SparkConf = new SparkConf() .setAppName("SparkStreamingKafkaReceiver") .setMaster("local[4]") //开启WAL日志,作用:保证数据源端的安全性,后期某些rdd的分区数据丢失了,是可以通过 血统+原始数据进行恢复 .set("spark.streaming.receiver.writeAheadLog.enable","true") //2、创建SparkContext对象 val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔 val ssc = new StreamingContext(sc,Seconds(5)) //设置checkpoint目录,用于保存接受到的数据 实际工作中是指向HDFS目录 ssc.checkpoint("./spark-receiver") //4、接受kafka中topic的数据 //指定zk服务地址 val zkQuorum="node1:2181,node2:2181,node3:2181" //指定消费者组id val groupId="spark-receiver" //指定topic相关信息 key:topic名称 value:表示一个receiver接收器使用多少个线程消费topic数据 val topics=Map("itcast" ->1) //(String, String): 第一个String就是消息的key,第二个String就是消息的value //val kafkaDstream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc,zkQuorum,groupId,topics) //构建了3个receiver接收器去接受数据,加快数据的接受速度 val kafkaDstreamList: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => { val kafkaDstream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics) kafkaDstream }) //ssc.union方法 把多个receiver接受器产生的Dstream汇总成一个Dstream val totalKafkaDstream: DStream[(String, String)] = ssc.union(kafkaDstreamList) // kafkaDstream.print() //5、获取topic中的真实数据 val data: DStream[String] = totalKafkaDstream.map(_._2) //6、切分每一行获取所有的单词 val words: DStream[String] = data.flatMap(_.split(" ")) //7、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //8、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //9、打印 result.print() //10、开启流式计算 ssc.start() ssc.awaitTermination() } }
-
2、总结
第一种方式使用了receiver接受器去接受数据,后期可以使用多个receiver接收器来加快接受的速度,使用了kafka高层次的消费者api,就是消息的偏移量由zk去维护。 默认会出现数据的丢失,可以启动WAL日志将接受到的数据同步写入到分布式文件系统HDFS上,保证数据源端它的安全性,后面就可以使用血统+原始数据,来重新计算恢复得到丢失的数据。 开启WAL日志: set("spark.streaming.receiver.writeAheadLog.enable","true") 需要设置数据保存目录: ssc.checkpoint("目录") 这种方式可以解决数据不丢失的问题,但是它解决不了数据被处理且只被处理一次。在这里由于更新偏移量到zk没有成功,导致数据正常消费成功了,没有把这个消费的标识记录下来,最后导致数据的重复消费。
7.2 KafkaUtils.createDirectStream
-
1、代码开发
package demo.kafka import kafka.serializer.StringDecoder import org.apache.spark.streaming.dstream.{DStream, InputDStream} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.kafka.KafkaUtils //todo:sparkStreaming整合kafka -------使用消费者低级api(消息的偏移量不在由zk去维护,由客户端程序自己去维护) object SparkStreamingKafkaDirect { def main(args: Array[String]): Unit = { //1、创建SparkConf对象 val sparkConf: SparkConf = new SparkConf() .setAppName("SparkStreamingKafkaDirect") .setMaster("local[4]") //2、创建SparkContext对象 val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔 val ssc = new StreamingContext(sc,Seconds(5)) //设置checkpoint目录 用于保存消息消费的偏移量 ssc.checkpoint("./spark-direct") //4、接受kafka中的数据 val kafkaParams=Map("bootstrap.servers" ->"node1:9092,node2:9092,node3:9092","group.id" ->"spark-direct") val topics=Set("itcast") //通过下面这种方式获取得到的Dstream它内部的rdd分区数跟kafka中topic的分区数相等。 val kafkaDstream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics) //5、获取topic中的真实数据 val data: DStream[String] = kafkaDstream.map(_._2) //6、切分每一行获取所有的单词 val words: DStream[String] = data.flatMap(_.split(" ")) //7、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //8、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //9、打印 result.print() //10、开启流式计算 ssc.start() ssc.awaitTermination() } }
7.3 打成jar包 集群运行
spark-submit --master spark://node1:7077 --class cn.itcast.kafka.SparkStreamingKafkaDirect --executor-memory 1g --total-executor-cores 2 spark_class12-1.0-SNAPSHOT.jar 对于实时处理来说什么情况下是比较理想的状态? 在当前批次时间内就把上一个批次的数据处理完成。 每隔10s去处理上一个10s的数据 第一个10s的数据 -------------------------------------> 1分钟 第二个10s的数据 -------------------------------------> 1分钟 第三个10s的数据 -------------------------------------> 1分钟 ..... 后面来的批次数据,一直会等待,会出现数据积压。 在企业中,我们正常通过本地开发代码程序,可以通过指定master为local,先本地测试,测试后数据没问题,可以把程序打成jar提交到集群中运行。 访问:master主机名:8080 可以先来一些资源参数进行测试: --executor-memory 5g --total-executor-cores 10 --executor-memory 5g --total-executor-cores 20 --executor-memory 10g --total-executor-cores 20 --executor-memory 10g --total-executor-cores 30 请关注我的公众号: