目录
SparkStreaming
Spark Streaming是什么?--是spark用于处理流式数据的部分
流程:输入源(kafka,flume,hdfs等)--->使用spark原语:map,reduce,join,window--->保存在数据库/hdfs
DStream简述--sparkstreaming的核心
(1)什么是DStream?
DStream是数据序列,DStream内部,每个时间区间收到的数据都作为RDD存在,而DStream是由这些RDD所组成的序列
(2)什么是背压机制
Spark 1.5以前版本:过设置静态配制参数“spark.streaming.receiver.maxRate”的值来实现限制Receiver的数据接收速率。问题是:producer数据生产高于maxRate,当前集群处理能力也高于maxRate,这就会造成资源利用率下降等问题。
Spark1.5版本开始:采用背压机制解决之前的问题
背压机制(即Spark Streaming Backpressure):根据JobScheduler反馈作业的执行信息来动态调整Receiver数据接收率。
通过属性“spark.streaming.backpressure.enabled”来控制是否启用背压机制,默认值false,即不启用。
(3)wordcount案例
1)目前用的算子,只能处理本批次数据的累加,不能统计所有批次总的单词个数。
2)DStream中批次之间计算独立。如果批次设置时间小于计算时间会出现计算任务叠加情况,需要多分配资源。通常情况,批次设置时间要大于计算时间。
object SparkStreaming01_WordCount {
def main(args: Array[String]): Unit = {
//1.初始化Spark配置信息
val sparkConf = new SparkConf().setAppName("SparkStreaming").setMaster("local[*]")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(sparkConf, Seconds(3))
//3.通过监控端口创建DStream,读进来的数据为一行行
val lineDStream = ssc.socketTextStream("hadoop102", 9999)
//3.1 将每一行数据做切分,形成一个个单词
val wordDStream = lineDStream.flatMap(_.split(" "))
//3.2 将单词映射成元组(word,1)
val wordToOneDStream = wordDStream.map((_, 1))
//3.3 将相同的单词次数做统计
val wordToSumDStream = wordToOneDStream.reduceByKey(_+_)
//3.4 打印
wordToSumDStream.print()
//4 启动SparkStreamingContext
ssc.start()
// 将主线程阻塞,主线程不退出
ssc.awaitTermination()
}
}
用netcat占领Hadoop102中的9999端口
DStream详细内容
(1) DStream创建
方式一:RDD队列方式
def main(args: Array[String]): Unit = {
//1.初始化Spark配置信息
val conf = new SparkConf().setAppName("SparkStreaming").setMaster("local[*]")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(conf, Seconds(4))
//3.创建RDD队列
val rddQueue = new mutable.Queue[RDD[Int]]()
//4.创建QueueInputDStream
// oneAtATime = true 默认,一次读取队列里面的一个数据
// oneAtATime = false, 按照设定的批次时间,读取队列里面数据
val inputDStream = ssc.queueStream(rddQueue, oneAtATime = false)
//5.处理队列中的RDD数据
val sumDStream = inputDStream.reduce(_+_)
//6.打印结果
sumDStream.print()
//7.启动任务
ssc.start()
//8.循环创建并向RDD队列中放入RDD
for (i <- 1 to 5) {
rddQueue += ssc.sparkContext.makeRDD(1 to 5)
Thread.sleep(2000)
ssc.awaitTermination()
}
结果展示:
-------------------------------------------
Time: 1603347444000 ms
-------------------------------------------
15
-------------------------------------------
Time: 1603347448000 ms
-------------------------------------------
30
-------------------------------------------
Time: 1603347452000 ms
-------------------------------------------
30
说明:如果一个批次中有多个RDD进入队列,最终计算前都会合并到一个RDD计算
方式二:自定义数据源接收器
创建自定义receiver的Streaming
val lineDStream = ssc.receiverStream(new CustomerReceiver("hadoop102", 9999))
=============================================================================
class CustomerReceiver(host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_ONLY) {
// receiver刚启动的时候,调用该方法,作用为:读数据并将数据发送给Spark
override def onStart(): Unit = {
//在onStart方法里面创建一个线程,专门用来接收数据
new Thread("Socket Receiver") {
override def run() {
receive()
}
}.start()
}
// 读数据并将数据发送给Spark
def receive(): Unit = {
// 创建一个Socket
var socket: Socket = new Socket(host, port)
// 字节流读取数据不方便,转换成字符流buffer,方便整行读取
val reader = new BufferedReader(new InputStreamReader(socket.getInputStream, StandardCharsets.UTF_8))
// 读取数据
var input: String = reader.readLine()
//当receiver没有关闭并且输入数据不为空,就循环发送数据给Spark
while (!isStopped() && input != null) {
store(input)
input = reader.readLine()
}
// 如果循环结束,则关闭资源
reader.close()
socket.close()
//重启接收任务
restart("restart")
}
override def onStop(): Unit = {}
}
方式三:kafka数据源
早期版本中,是专门有一个executor去接受数据,然后发给其他executor做计算。存在接受速率与计算速率不同问题
现在直接是计算的executor去主动消费kafka的数据,速度自身控制。
注意:
0-8 ReceiverAPI offset默认存储在:Zookeeper中
0-8 DirectAPI offset默认存储在:CheckPoint
手动维护:MySQL等有事务的存储系统
0-10 DirectAPI offset默认存储在:_consumer_offsets系统主题
手动维护:MySQL等有事务的存储系统
def main(args: Array[String]): Unit = {
//1.创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("sparkstreaming").setMaster("local[*]")
//2.创建StreamingContext
val ssc = new StreamingContext(sparkConf, Seconds(3))
//3.定义Kafka参数:kafka集群地址、消费者组名称、key序列化、value序列化
val kafkaPara: Map[String, Object] = Map[String, Object](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop102:9092,hadoop103:9092,hadoop104:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "atguiguGroup",
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> "org.apache.kafka.common.serialization.StringDeserializer",
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer]
)
//4.读取Kafka数据创建DStream
val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
ssc,
LocationStrategies.PreferConsistent, //优先位置
ConsumerStrategies.Subscribe[String, String](Set("testTopic"), kafkaPara)// 消费策略:(订阅多个主题,配置参数)
)
//5.将每条消息(KV)的V取出
val valueDStream: DStream[String] = kafkaDStream.map(record => record.value())
//6.计算WordCount
valueDStream.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
.print()
//7.开启任务
ssc.start()
ssc.awaitTermination()
}
(2) DStream的转换(类似于RDD的转换算子)
方式一:无状态转换----->每个批次相互独立,自己算自己的。
1)常规无状态转化操作
函数名称 | 目的 | Scala**示例** |
---|---|---|
map() | 对DStream中的每个元素应用给定函数,返回由各元素输出的元素组成的DStream。 | ds.map(x=>x + 1) |
flatMap() | 对DStream中的每个元素应用给定函数,返回由各元素输出的迭代器组成的DStream。 | ds.flatMap(x => x.split(" ")) |
filter() | 返回由给定DStream中通过筛选的元素组成的DStream | ds.filter(x => x != 1) |
repartition() | 改变DStream的分区数 | ds.repartition(10) |
reduceByKey() | 将每个批次中键相同的记录规约。 | ds.reduceByKey( (x, y) => x + y) |
groupByKey() | 将每个批次中的记录根据键分组。 | ds.groupByKey() |
2)Transform
通过Transform可以将DStream每一批次的数据直接转换为RDD的算子操作
方式二:有状态转换
1)updateStateByKey()用于键值对形式的DStream,可以记录历史批次状态。例如可以实现累加WordCount。
注意:使用updateStateByKey需要对检查点目录进行配置,会使用检查点来保存状态。
2)window(windowLength, slideInterval): 基于对源DStream窗口的批次进行计算返回一个新的DStream。
3)reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]):当在一个(K,V)对的DStream上调用此函数,会返回一个新(K,V)对的DStream,此处通过对滑动窗口中批次数据使用reduce函数来整合每个key的value值。
4)reduceByKeyAndWindow(反向Reduce)
(3) DStream的输出(类似于RDD的行动算子)
Ø saveAsTextFiles(prefix, [suffix]):以text文件形式存储这个DStream的内容。每一批次的存储文件名基于参数中的prefix和suffix。“prefix-Time_IN_MS[.suffix]”。
Ø saveAsObjectFiles(prefix, [suffix]):以Java对象序列化的方式将DStream中的数据保存为 SequenceFiles 。每一批次的存储文件名基于参数中的为"prefix-TIME_IN_MS[.suffix]"。
Ø saveAsHadoopFiles(prefix, [suffix]):将Stream中的数据保存为 Hadoop files。每一批次的存储文件名基于参数中的为"prefix-TIME_IN_MS[.suffix]"。
注意:以上操作都是每一批次写出一次,会产生大量小文件,在生产环境,很少使用。
Ø print():在运行流程序的驱动结点上打印DStream中每一批次数据的最开始10个元素。这用于开发和调试。
Ø foreachRDD(func):这是最通用的输出操作,即将函数func用于产生DStream的每一个RDD。其中参数传入的函数func应该实现将每一个RDD中数据推送到外部系统,如将RDD存入文件或者写入数据库。
在企业开发中通常采用foreachRDD(),它用来对DStream中的RDD进行任意计算。这和transform()有些类似,都可以让我们访问任意RDD。在foreachRDD()中,可以重用我们在Spark中实现的所有行动操作(action算子)。比如,常见的用例之一是把数据写到如MySQL的外部数据库中。