spark_day04学习笔记
1、课程目标
- 1、掌握sparkStreaming原理和架构
- 2、掌握Dstream常用的操作
- 3、掌握flume整合SparkStreaming
- 4、掌握kafka整合SparkStreaming
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、容错性
- 可以保证数据被处理且只被处理一次
- 恰好一次语义
- 可以保证数据被处理且只被处理一次
- 3、融合到spark体系
- 可以把sparkstreaming实时处理与批处理和交互式查询结合使用
3、sparkStreaming原理
3.1 sparkStreaming计算原理
sparkStreaming是一个流式的批处理引擎,以某一时间间隔的批量处理,当时间间隔缩短到秒级,我们就可以看成是实时处理。
3.2 sparkStreaming计算流程
sparkStreaming是以某一时间的间隔批量处理,把源源不断产生的数据,按照时间间隔,划分成了很多个短小的作用,每一个作业中都封装一段数据,每一个批次的数据可以看成是一个Dstream.
Dstream内部封装了RDD,rdd内部有很多个分区,分区里面才是真正的数据,后期还是以分区为单位就是一个task线程去运行任务。
Dstream有很多大量的 transformation操作,我们对Dstream做了大量的转换操作,其本质是作用在内部的rdd上也做了类似于这些transformation操作。
3.3 sparkStreaming容错性
Dstream内部是封装了RDD,这里还是可以依赖于RDD自身的容错性:
可以使用血统lineage这层关系来进行重新计算恢复得到某些rdd部分丢失的分区数据。
val rdd2=rdd1.map(x=>(x,1)
要想恢复数据这里是需要这2个条件: 血统+数据源
对于sparkStreaming应用程序来说,在接受在数据之后,它会进行多份拷贝到其他机器,来保证数据源的安全性,后期方便进行数据恢复。
3.4 sparkStreaming实时性
storm是来一条数据就处理一条,实时性是比较高---->从数据源产生数据到最后经过处理之后看到结果数据时间很短。
sparkStreaming是以某一时间间隔的批量处理,数据并不是来一条就处理一条,实时性是比较低,延迟是比较高。
后期如果公司用到了实时处理场景,具体使用哪一个实时处理框架,还是要看一定业务需求:
(1)如果对实时性要求比较高,这里可以优先考虑storm
(2)如果对实时性的要求不是特别高,允许数据出现一定的延迟,这个时候可以优先考虑sparkStreaming实现。
4、Dstream
4.1 Dstream是什么
Discretized Stream是Spark Streaming的基础抽象,代表持续性的数据流和经过各种Spark算子操作后的结果数据流。在内部实现上,DStream是一系列连续的RDD来表示。每个RDD含有一段时间间隔内的数据。
4.2 Dstream操作分类
- 1、transformation(转换)
- 可以实现把一个Dstream转换生成一个新的Dstream,它也是延迟加载,不会立即触发任务的运行
- 它是类似于rdd的transformation
- 可以实现把一个Dstream转换生成一个新的Dstream,它也是延迟加载,不会立即触发任务的运行
- 2、outputOperation (输出)
- 它会触发任务的真正运行
- 它是类似于rdd中的action
- 它会触发任务的真正运行
5、Dstream操作实战
- 1、引入依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.1.3</version>
</dependency>
5.1 利用sparkStreaming接受socket数据实现单词统计
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SItP9LJM-1593305789782)(基于单词统计来分析sparkStreaming处理流程.png)]
- 1、代码开发
package cn.itcast.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 data: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)
//5、切分每一行获取所有的单词
val words: DStream[String] = data.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、代码开发
- updateStateByKey使用
package cn.itcast.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,1)(hadoop,1)(hadoop,1)---->List(1,1,1)
//historyValues:之前所有的批次每一个单词出现的总次数:Option类型:Some(有值) None(没有)
def updateFunc(currentValues:Seq[Int], historyValues:Option[Int]):Option[Int] ={
val newValues: Int = currentValues.sum + historyValues.getOrElse(0)
Some(newValues)
}
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 data: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)
//5、切分每一行获取所有的单词
val words: DStream[String] = data.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数据,使用开窗函数实现单词统计
- 1、代码开发
- reduceByKeyAndWindow
package cn.itcast.socket
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
//todo;利用sparkStreaming接受socket数据,使用开窗函数实现单词统计
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 data: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)
//5、切分每一行获取所有的单词
val words: DStream[String] = data.flatMap(_.split(" "))
//6、每个单词计为1
val wordAndOne: DStream[(String, Int)] = words.map((_,1))
//7、相同单词出现的1累加
//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接受socket数据,实现一定时间内的热门词汇统计
- 1、代码开发
- transform
package cn.itcast.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:利用sparkStreaming接受socket数据实现一定时间内热门词汇统计
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 data: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.72.110",9999)
//5、实现单词统计
val result: DStream[(String, Int)] = data.flatMap(_.split(" ")).map((_,1)).reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(10),Seconds(10))
庆华 庆华 亚明 亚明 亚明 郭冰 卡卡 卡卡 卡卡 卡卡
庆华,2
亚明,3
郭冰,1
卡卡,4
排序 求前三
DStream = 一个时间段内的rdd
sortby
卡卡,4
亚明,3
庆华,2
郭冰,1
take
卡卡,4
亚明,3
庆华,2
//6、按照单词出现的次数降序
val finalResult: DStream[(String, Int)] = result.transform(rdd => {
//通过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")
})
//7、打印
finalResult.print()
ssc.start()
ssc.awaitTermination()
}
}
6、sparkStreaming整合flume
6.1 拉模式(优化考虑拉模式)
需要把spark-streaming-flume-sink_2.11-2.1.3.jar放入到flume的lib目录下
同时需要把flume自带的scala2.10这个jar包替换成scala2.11jar包
- 1、修改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
- 2、启动flume
bin/flume-ng agent -n a1 -c conf -f conf/flume-poll-spark.conf -Dflume.root.logger=info,console
-
3、代码开发
- 1、引入依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-flume_2.11</artifactId> <version>2.1.3</version> </dependency>
- 2、代码开发
package cn.itcast.flume import java.net.InetSocketAddress import org.apache.spark.storage.StorageLevel import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.{SparkConf, SparkContext} 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 val ssc = new StreamingContext(sc,Seconds(5)) //4、通过poll拉模式进行整合 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 一个event对象中包括 {"headers":xxxx,"body":xxxx} 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()
ssc.start() ssc.awaitTermination()
}
}
6.2 推模式
-
1、修改flume配置
#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
a1.sinks.k1.hostname=192.168.26.39
a1.sinks.k1.port = 8888
a1.sinks.k1.batchSize= 2000
* 2、启动flume
bin/flume-ng agent -n a1 -c conf -f conf/flume-push-spark.conf -Dflume.root.logger=info,console
* 3、代码开发
~~~scala
package cn.itcast.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----推模式
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
val ssc = new StreamingContext(sc,Seconds(5))
//4、实现推模式整合 这里的hostname是sparkStreaming应用程序所在的地址
val stream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createStream(ssc,"192.168.26.39",8888)
//5、获取flume中数据
//flume中数据传输的最小单元:Event 一个event对象中包括 {"headers":xxxx,"body":xxxx}
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()
ssc.start()
ssc.awaitTermination()
}
}
7、sparkStreaming整合kafka
7.1 KafkaUtils.createStream
-
1、描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bKglqFxR-1593305789784)(嗲一套api逻辑.png)]
这里使用了kafka消费者高级api,使用了receiver接受器去接受数据,消息的偏移保存在zk中,默认这种方式会导致数据的丢失,可以开启wal日志之后,把接受到的数据同步写入到分布式文件系统中,保证数据源端的安全,后期可以通过血统+数据源去恢复某些丢失的数据。
但是这种机制它保证不了数据被处理且只被处理一次,也就是说在这种情况下会出现数据的重复处理。
- 2、代码开发
package cn.itcast.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接受器接受数据,使用消费者高级api(就是把消息的偏移量保存在zk中)
object SparkStreamingKafkaReceiver {
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf()
.setAppName("SparkStreamingKafkaReceiver")
.setMaster("local[4]")
//默认数据会丢失:原因:数据源端的安全性没有得到保障,导致后期通过血统进行恢复的时候拿不到原始的数据,进而恢复不了
//开启WAL日志 把最开始接受到的数据同步的写入到分布式文件系统中,保障数据源端的安全性
.set("spark.streaming.receiver.writeAheadLog.enable","true")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、创建StreamingContext
val ssc = new StreamingContext(sc,Seconds(5))
//设置checkpoint目录,用于保存接受到的原始数据
ssc.checkpoint("./spark-receiver")
//4、设置zk地址
val zkQuorum="node1:2181,node2:2181,node3:2181"
//5、设置消费者组id
val groupId="spark-receiver"
//6、设置topic相关信息 key:表示topic的名称 value:表示每一个receiver接收器使用多少个线程消费数据
val topics=Map("itcast" -> 1 )
//(String, String) Dstream内部每一个元素是一个元组,元组有2位,第一个String表示消息的key,第二个String表示消息的内容
//val stream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc,zkQuorum,groupId,topics)
//使用多个receiver接收器去接受数据
val totalDstream: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => {
val stream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics)
stream
})
//通过ssc调用union方法,把多个Dstream的结果合并在一起生成一个新的Dstream
val unionDstream: DStream[(String, String)] = ssc.union(totalDstream)
//7、获取kafka中的topic数据
val data: DStream[String] = unionDstream.map(x=>x._2)
//8、切分每一行
val words: DStream[String] = data.flatMap(_.split(" "))
//9、每个单词计为1
val wordAndOne: DStream[(String, Int)] = words.map((_,1))
//10、相同的单词出现的1累加
val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
//11、打印
result.print()
ssc.start()
ssc.awaitTermination()
}
}
7.2 KafkaUtils.creataDirectStream
- 1、代码开发
package cn.itcast.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---- 不在基于receiver接收器接收数据,直接连接上kafka集群,把对应的数据获取得到
//消息的偏移量不在由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
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")
//5、指定topic名称
val topics=Set("itcast")
val kafkaDstream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics)
//6、获取topic的数据
val data: DStream[String] = kafkaDstream.map(_._2)
//7、切分每一行
val words: DStream[String] = data.flatMap(_.split(" "))
//8、每个单词计为1
val wordAndOne: DStream[(String, Int)] = words.map((_,1))
//9、相同的单词出现的1累加
val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
//10、打印
result.print()
ssc.start()
ssc.awaitTermination()
}
}
8、程序打成jar包提交到集群中运行
spark-submit --master spark://node1:7077 --class cn.itcast.kafka.SparkStreamingKafkaDirect --executor-memory 1g --total-executor-cores 4 spark_class18-1.0-SNAPSHOT.jar
后期再实际工作中,前期都是基于idea开发代码,然后在本地运行,测试下代码处理逻辑对不对,如果有问题,及时修改,如果没有问题,就把代码改造一下,然后提交到集群中运行。
任务在实际运行的时候,需要向master申请资源,这个资源到底申请多少是合理的?
对于sparkStreaming流式处理来说,可以找到一个比较理想的状态?
就是当前批次时间之内就把该批次数据处理完成。
第一个5s的批次的数据来了: ---------------------------------------> 需要1分钟
第二个5s的批次的数据来了: ---------------------------------------> 需要1分钟
第三个5s的批次的数据来了: ---------------------------------------> 需要1分钟
....
数据会出现大量的积压,最后数据处理的延迟是非常高。
下面进行测试,先给定一些资源,然后把程序提交到spark集群中运行,随后访问spark集群的web管理界面
master主机名:8080 可以观察到任务运行的状态
测试参数
--executor-memory 10g --total-executor-cores 20
--executor-memory 10g --total-executor-cores 30
--executor-memory 15g --total-executor-cores 30
--executor-memory 15g --total-executor-cores 40