2021-05-06

目录

 

 

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中通过筛选的元素组成的DStreamds.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的外部数据库中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值