SparkStreaming笔记分享

1、SparkStreaming的概述
    1、什么是SparkStreaming?
        SparkStreaming是一个微批次的流式处理的spark组件
    2、什么是DStream
        SparkStreaming是等待接收一定时间后,将这段时间的数据统一处理,这段时间内接收的数据称之为一个批次,sparkstreaming会将一个批次的数据封装成RDD。
        DStream是离散化流,DStream中流动的是一个批次的RDD,RDD里面时一个个批次的数据。
    3、背压机制
        sparkstreaming每次是处理一个批次的数据[sparkstreaming处理批次数据:是先处理前一个批次,再处理后一个批次,批次之间执行是串行]
            如果一个批次计算时间>批次时间,此时会出现批次积压的问题。
            如果一个批次的计算时间<批次时间,会出现资源浪费的问题。
        sparkstreaming提供了一个背压机制,能够根据数据的处理时间动态的调整数据的拉取速率。
        可以在创建StreamingContext的时候再SparkConf中通过spark.streaming.backpressure.enabled开启。
    4、WC案例
            def main(args: Array[String]): Unit = {

            //1、创建StreamingContext
            val conf = new SparkConf().setMaster("local[4]").setAppName("test")
            val ssc = new StreamingContext(conf,Seconds(5))
            ssc.sparkContext.setLogLevel("error")
            //2、读取数据
            val ds = ssc.socketTextStream("hadoop102",9999)
            //3、切割+压平
            val ds2 = ds.flatMap(x=>{
              //Thread.sleep(6000)
              x.split(" ")
            })
            //4、统计单词个数
            val ds3 = ds2.map( (_,1 ) )

            val ds4 = ds3.reduceByKey(_+_)
            //5、结果展示
            ds4.print()

            //6、启动streaming程序
            ssc.start()

            //7、阻塞
            ssc.awaitTermination()
            }
2、DStream的创建
    1、RDD队列数据源
        val queue:mutable.Queue[RDD[...]] = ...
        ssc.queueStream(queue[,oneAtTime=true])
            oneAtTime=true代表一个批次只从队列中取出一个RDD处理
            oneAtTime=false代表一个批次将批次时间内所有RDD取出union之后统一处理
    2、自定义数据源
        1、自定义:
            1、创建class继承Receiver[数据类型](存储级别)
            2、重写抽象方法[onStart、onStop]
        2、使用:
            ssc.receiverStream(自定义数据对象)
        代码
        class MySocketSource(val host:String,val port:Int) extends Receiver[String](StorageLevel.MEMORY_AND_DISK) {
          /**
            * receiver启动的时候调用
            */
          override def onStart(): Unit = {

            new Thread(){
              override def run(): Unit = {
                receive()
              }

            }.start()
          }

          /**
            * 读取数据
            */
          def receive(): Unit ={
            val socket = new Socket(host,port)

            val br = new BufferedReader(new InputStreamReader(socket.getInputStream))

            var line:String = br.readLine()

            while( line !=null ){
              //保存数据等到积累一个批次之后统一处理
              store(line)

              line = br.readLine()
            }

            br.close()

            socket.close()
          }

          /**
            * receiver停止时调用
            */
          override def onStop(): Unit = {

          }
        }
    3、kafka数据源
        kafka数据源采用direct模式    (spark3.0.0以上版本只有Direct模式)
            DirectAPI由Executor拉取Topic分区数据,速度由自身控制]    ReceiverAPI[需要一个专门的Executor去接收数据,然后发送给其他的Executor计算]
        //设置拉取数据的所有的topic名称
        val topics = Array("spark_streaming")
        //设置消费者组的配置
        val kafkaparams = Map[String,Object](
          //指定kafka集群地址
          "bootstrap.servers"-> "hadoop102:9092,hadoop103:9092,hadoop104:9092",
          //指定key的反序列化器
          "key.deserializer"-> "org.apache.kafka.common.serialization.StringDeserializer",
          //指定value的反序列化器
          "value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
          //指定消费者组的id
          "group.id" -> "gg1",
          //指定消费者组第一次消费topic的时候从哪个位置开始消费
          "auto.offset.reset"->"earliest",
          //是否自动提交offset
          "enable.auto.commit" -> "true"
        )
        val ds = KafkaUtils.createDirectStream[String,String]
            (ssc,
            //LocationStrategies:    PreferBrokers[执行器和kafka在一个节点,本地]    PreferConsistent[所有的Executor一致性分配分区(均匀分配)]
            LocationStrategies.PreferConsistent,
            ConsumerStrategies.Subscribe[String,String](topics,kafkaparams))
3、DStream转换
    状态: 之前批次的数据/结果
    
    1、无状态转换[只计算当前批次]
        map/flatMap/filter/reduceByKey/sortBy...
        transform(func: RDD[DStream中元素类型]=>RDD[B]): 一对一转换[DStream中一个RDD转换之后得到新RDD]
            transform里面的函数是针对每个批次的RDD操作
    2、有状态转换[计算当前与之前批次/结果]
        updateStateByKey(func: ( Seq[DStream Value值类型],Option[S] )=>Option[S]): 根据key分组统计每个key的全局[从启动到当前批次]的结果
            updateStateByKey函数第一个参数是代表分组之后每个key所有的value值的集合
            updateStateByKey函数第二个参数是代表分组之后每个key之前批次的统计结果
        updateStateByKey代码
            val ds4 = ds3.updateStateByKey[Int]( (currentBachValues:Seq[Int], state:Option[Int]) =>{

              //得到当前批次该单词总次数
              val currentNum = currentBachValues.sum
              //得到之前批次的该单词的总次数
              val beforeNum = state.getOrElse(0)

              Some( currentNum + beforeNum )
            } )
        window(窗口长度,滑动长度): 窗口[sparkstreaming会将多个批次的数据组成一个窗口统一计算]
            窗口长度与滑动长度必须是批次时间的整数倍
        window代码
                val ds4 = ds3.window(Seconds(15),Seconds(5)).reduceByKey(_+_)
        reduceByKeyAndWindow(func: (Value值类型,Value值类型)=>Value值类型,窗口长度,滑动长度):将窗口内的数据进行reduceByKey
            reduceByKeyAndWindow( _+_ ,windowDuration = Seconds(15),Seconds(5))        //这里因为参数太多,不能每个都简写,需要指定为带名参数
        reduceByKeyAndWindow代码
            //窗口长度与滑动长度必须是批次时间整数倍
            val ds4 = ds3.reduceByKeyAndWindow( _+_ ,windowDuration = Seconds(15),Seconds(5))
        反向reduceByKeyAndWindow(划入func,划出func,窗口长度,滑动长度)
            模拟场景: 窗口长度很长,滑动长度很小,此时上下两个窗口中包含大量重复批次,此时可以通过上一个创建结果-滑出批次+滑入批次计算出本批次的结果,从而提高计算效率
            reduceByKeyAndWindow(
                (a: Int, b: Int) => (a + b),
                (x: Int, y: Int) => (x - y),
                ,windowDuration = Seconds(15),Seconds(5))
        countByWindow(windowLength, slideInterval)[窗口长度,滑动长度]: 返回一个滑动窗口计数流中的元素个数;
        reduceByWindow(func, windowLength, slideInterval)[聚合函数,窗口长度,滑动长度]: 通过使用自定义函数整合滑动区间流元素来创建一个新的单元素流;
    3、foreachRDD(func)::是最通用的输出操作,将函数func用于产生DStream的每一个RDD。其中参数传入的函数func应该实现将每一个RDD中数据推送到外部系统,如将RDD存入文件或者写入数据库。
        注:DStream算子中的函数参数如果是RDD,则函数不是在Task中执行的。只是构建好流程,触发了RDD的行动算子后,需将代码转成Job提交到Task,RDD的算子内的代码才是Task执行的逻辑。
4、优雅关闭
    问题:如一个批次的数据处理到一半时把程序强制关闭,下次运行程序时已经处理了的数据这时已经拉不到了(相当于丢数据了),因为已经在Kafka内部Topic中保存了偏移量。
        流式任务需要7*24小时执行,但是有时涉及到升级代码需要主动停止程序,但是分布式程序没办法做到一个个进程去杀死,所以需要配置优雅关闭。
    关闭方式:
        使用外部文件系统来控制内部程序关闭。
    ssc.stop(stopSparkContext,stopGracefully):
        stopSparkContext:[恒定为ture]    停止关联SparkContext
        stopGracefully:    是否优雅的停止,等待所有接受到的数据处理完成
    代码
        //启动streaming程序
        ssc.start()
        //可以通过改变外部的动作来通知sparkstreaming停止程序
        //    1、sparkstreaming可以监控mysql某个表某个字段的值,一旦外部改变该值则停止程序
        //    2、sparkstreaming可以监控HDFS某个目录,一旦外部删除了该目录停止程序
        //获取HDFS文件系统
        val fs = FileSystem.get(new URI("hdfs://hadoop102:8020"),new Configuration())
        val path = new Path("hdfs://hadoop102:8020/input")
        while( fs.exists(path) ){
          Thread.sleep(2000)
        }

        ssc.stop(true,true)
    
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值