SparkStreaming_04
1.背压机制
背压机制(即Spark Streaming Backpressure): 根据JobScheduler反馈作业的执行信息来动态调整Receiver数据接收率。
2.DStream
在内部实现上,DStream是一系列连续的RDD来表示。每个RDD含有一段时间间隔内的数据。
ReceiverAPI:需要一个专门的Executor去接收数据,然后发送给其他的Executor做计算。存在的问题,接收数据的Executor和计算的Executor速度会有所不同
DirectAPI:是由计算的Executor来主动消费Kafka的数据,速度由自身控制。
无状态转化操作就是把简单的RDD转化操作应用到每个批次上
3.Spark Streamming 有哪几种方式消费Kafka中的数据?
Receiver:
- 高阶API,获取的数据存储在Executor内存中
- Receiver是单点读数据,如果挂掉,程序不能运行
- 避免数据丢失启用预写入日志机制
Direct:
- 低阶API,每隔一段时间,去kafka读取一批数据
- 简化并行度,rdd的分区数量=topic的分区数量
- 不需要开启预写入机制,通过Kafka的副本进行恢复
对比:
- 基于receiver的方式高阶AP,是通过ZooKeeper保存消费的offset,Spark与ZooKeeper之间可能是不同步的,无法保证数据被精确处理一次
- 基于direct的方式,自己负责追踪消费的offset,并保存在checkpoint中,保证数据精确消费一次
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>2.3.1</version>
</dependency>
3.1操作
-
启动hadoop集群
-
启动zookeeper
bin/zkServer.sh start
-
启动kafka
开启
nohup bin/kafka-server-start.sh config/server.properties &
nohup bin/kafka-server-start.sh config/server.properties &
nohup bin/kafka-server-start.sh config/server.properties &关闭
bin/kafka-server-stop.sh stop创建主题
bin/kafka-topics.sh --zookeeper hadoop101:2181 --create --replication-factor 3 --partitions 1 --topic first删除topic
bin/kafka-topics.sh --zookeeper hadoop101:2181 --delete --topic first创建主题
bin/kafka-topics.sh --zookeeper hadoop101:2181 --create --replication-factor 3 --partitions 3 --topic second生产消息
bin/kafka-console-producer.sh --broker-list hadoop101:9092 --topic second消费消息
bin/kafka-console-consumer.sh --zookeeper hadoop101:2181 --from-beginning --topic second
3.2 direct代码
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.kafka010.{KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
object KafkaDirectorDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("demo1").setMaster("local[*]")
val ssc = new StreamingContext(conf, Seconds(5))
//设置数据检查点进行累计计算
ssc.checkpoint(".")
//创建读取kafka的参数
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "hadoop101:9092,hadoop102:9092,hadoop103:9092", //用于初始化链接到集群的地址
"key.deserializer" -> classOf[StringDeserializer], //key序列化
"value.deserializer" -> classOf[StringDeserializer], //value序列化
"group.id" -> "test-consumer-group", //用于标识这个消费者属于哪个消费团体
"auto.offset.reset" -> "latest", //偏移量 latest自动重置偏移量为最新的偏移量
"enable.auto.commit" -> (false: java.lang.Boolean) //如果是true,则这个消费者的偏移量会在后台自动提交
)
//kafka 设置kafka读取topic
val topics = Array("third")
//LocationStrategies.PreferConsistent任务尽量均匀分布在各个executor节点
// 创建DStream,返回接收到的输入数据
// LocationStrategies:根据给定的主题和集群地址创建consumer
// LocationStrategies.PreferConsistent:持续的在所有Executor之间分配分区
// ConsumerStrategies:选择如何在Driver和Executor上创建和配置Kafka Consumer
// ConsumerStrategies.Subscribe:订阅一系列主题
//createDirectStream[String,String] 指定消费kafka的message的key/value的类型
//ConsumerStrategies.Subscribe[String,String]指定key/value的类型
val dStream = KafkaUtils.createDirectStream(ssc, LocationStrategies.PreferConsistent, Subscribe[String, String](topics, kafkaParams))
val rdd01 = dStream.map(x => (x.key(), x.value()))
val words = rdd01.map(x => x._2).flatMap(_.split(" ")).map(x => (x, 1)).reduceByKey(_ + _)
rdd01.print()
words.print()
ssc.start()
ssc.awaitTermination()
}
}
4.DStream算子
NetCat安装链接
nc -lp 9999 开启端口测试
4.1count,reduce,foreachRdd
object Demo1 {
def main(args: Array[String]): Unit = {
//注意运输模式为local的时候必须至少设置2个core, local[2]
//接收socket的数据,需要一个对应的接收器,这个接收器就会使用cpu核
//处理数据就需要一个执行器,这个执行器也会使用cpu核
val conf = new SparkConf().setAppName("wordcount").setMaster("local[2]")
//需要两个参数 第一个是 SparkConf 第二个是时间间隔,每个批次的数据的时间范围的长度
val ssc = new StreamingContext(conf, Seconds(5)) //每5秒是一个时间批次
//接收一个socket端口的数据
//两个参数 第一参数:hostname或者是IP地址 第二个参数:端口号
val lines = ssc.socketTextStream("hadoop101", 9999);
// val word = lines.flatMap(_.split(" ")).map(word=>(word,1)).reduceByKey(_+_)
// lines.print()
// word.print()
//同一个批次中RDD的个数,和RDD内容无关
// val ds01 = lines.count()
// ds01.print()
//同一个批次中的RDD做拼接
// val ds02 = lines.reduce(_+_)
// val ds02 = lines.reduce((a,b)=>(a+"~"+b))
// ds02.print()
lines.foreachRDD { //foreachRDD内的代码在Driver端执行
(rdd, time) => {
//RDD上的算子
val rdd01 = rdd.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
rdd01.foreach(t => println(t, time))
}
}
ssc.start()
ssc.awaitTermination()
}
}
4.2updateStateByKey,reduceByKeyAndWindow
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
object Demo2 {
def main(args: Array[String]): Unit = {
//注意运输模式为local的时候必须至少设置2个core, local[2]
//接收socket的数据,需要一个对应的接收器,这个接收器就会使用cpu核
//处理数据就需要一个执行器,这个执行器也会使用cpu核
val conf = new SparkConf().setAppName("wordCount").setMaster("local[2]")
//需要两个参数 第一个是 SparkConf 第二个是时间间隔,每个批次的数据的时间范围的长度
val ssc = new StreamingContext(conf, Seconds(5)) //每5秒是一个时间批次
ssc.checkpoint(".")
//接收一个socket端口的数据
//两个参数 第一参数:hostname或者是IP地址 第二个参数:端口号
val lines = ssc.socketTextStream("Hadoop101", 9999)
val words = lines.flatMap(_.split(" ")).map((_, 1))
// 定义更新状态方法,参数values为当前批次单词频度,state为以往批次单词频度
val updateFunc = (vales: Seq[Int], state: Option[Int]) => {
val currentCount = vales.fold(0)(_ + _)
val previousCount = state.getOrElse(0)
Some(currentCount + previousCount)
}
// val stateDstream = words.updateStateByKey[Int](updateFunc)
// stateDstream.print()
// val wordCount = words.reduceByKeyAndWindow(_+_,Seconds(15))
// wordCount.print()
// val wordCount1 = words.reduceByKeyAndWindow((a:Int,b:Int)=>a+b,Seconds(15),Seconds(10))
// wordCount1.print()
// val wordCount2 = words.countByWindow(Seconds(15),Seconds(5))
// wordCount2.print()
// val wordcount3 = words.countByValueAndWindow(Seconds(15),Seconds(5))
// wordcount3.print()
val wordcount4 = words.window(Seconds(20))
wordcount4.print()
ssc.start()
ssc.awaitTermination()
}
}
4.3count,countByValue
object Demo3 {
def main(args: Array[String]): Unit = {
//注意运输模式为local的时候必须至少设置2个core, local[2]
//接收socket的数据,需要一个对应的接收器,这个接收器就会使用cpu核
//处理数据就需要一个执行器,这个执行器也会使用cpu核
val conf = new SparkConf().setAppName("wordcount").setMaster("local[2]")
//需要两个参数 第一个是 SparkConf 第二个是时间间隔,每个批次的数据的时间范围的长度
val ssc = new StreamingContext(conf, Seconds(5))
//每5秒是一个时间批次
//接收一个socket端口的数据
//两个参数 第一参数:hostname或者是IP地址 第二个参数:端口号
val lines = ssc.socketTextStream("Hadoop101", 9999)
//每个RDD所含元素的个数,统计所有value的共出现的个数
val ds01 = lines.count()
//统计每个RDD中,统计给每个value的出现的个数
val ds02 = lines.countByValue()
ds01.print()
ds02.print()
ssc.start()
ssc.awaitTermination()
}
}
4.4reduceByKeyAndWindow
object Demo5 {
def main(args: Array[String]): Unit = {
//注意运输模式为local的时候必须至少设置2个core, local[2]
//接收socket的数据,需要一个对应的接收器,这个接收器就会使用cpu核
//处理数据就需要一个执行器,这个执行器也会使用cpu核
val conf = new SparkConf().setAppName("wordcount").setMaster("local[2]")
//需要两个参数 第一个是 SparkConf 第二个是时间间隔,每个批次的数据的时间范围的长度
val ssc = new StreamingContext(conf, Seconds(5))
ssc.checkpoint(".")
//每5秒是一个时间批次
//接收一个socket端口的数据
//两个参数 第一参数:hostname或者是IP地址 第二个参数:端口号
val lines = ssc.socketTextStream("Hadoop101", 9999)
val words = lines.flatMap(_.split(" ")).map(x=>(x,1))
//window(窗口时长) 窗口时长必须是批次的整数倍
// val ds01 = lines.window(Seconds(10)) //返回 窗口时长 这个范围内的数据 窗口时长控制每次计算最近的多少个批次的数据
// ds01.print()
//window(窗口时长,滑动时长)
// val ds02 = lines.window(Seconds(15),Seconds(10))
// ds02.print()
//reduceByKeyAndWindow(自定义函数,窗口时长,滑动时长)
// val ds03 = words.reduceByKeyAndWindow((a:Int,b:Int)=>a+b,Seconds(15),Seconds(10))
// ds03.print()
val ds04 = words.countByWindow(Seconds(15),Seconds(10))
val ds05 = words.countByValueAndWindow(Seconds(15),Seconds(10))
ds04.print()
ds05.print()
ssc.start()
ssc.awaitTermination()
}
}
2021.11.26 10::42