SparkStreaming

storm和 SparkStreaming的区别
  1. Storm 是纯实时处理数据, SparkStreaming 微批处理数据,可以通过控制间隔时间做到实时处理.sparkStreaming 相对于storm来说,吞吐量大
  2. storm擅长处理简单的汇总型业务,sparkStreaming擅长处理复杂业务,storm相对于sparkStreaming来说轻量级,SparkStreaming中可以使用core或者sql或者机器学习
  3. storm的事务与SparkStreaming不同,sparkStreaming可以管理事务
  4. storm支持动态的资源调度,spark也支持
sparkStreaming简易模型

在这里插入图片描述
SparkStreaming是7x24小时不间断运行,sparkStreaming启动之后,首先会生成一个job,这个job有一个receiver task来接受数据。task每隔一段时间将接收来的数据封装到一个batch中,这段时间就是 batchInterval,生成的每个batch又被封装到一个RDD中,这个RDD又被分装到一个DStream中,sparkStreaming底层操作的就是DStream.DStream有自己的Transformation类算子,懒执行,需要DStream的OutputOperator类算子触发执行
假设 batchInterval=5s,
生成一个DStream时间是5s,集群处理一批数据时间是3s,0-5s集群一直接收数据,5-8s一边接收一边处理,8-10s只是接收数据,这样每一批次会造成集群的“休息”2s,集群资源利用不充分
如果集群处理一批次的时间是8s,0-5s只是接收数据,5-10一边接收数据,一边处理数据,10-13s一边接收数据,一边处理数据,这样批次数据越堆积越多,如果接收来的数据放在内存中,会有orm问题,如果内存不足放磁盘,也会加大数据处理的延迟问题
最好状态是:batchInterval=5s,集群处理一批次数据时间也是5s

sparkstreaming模拟
  • SparkStreaming 注意:
  • 1.需要设置local[2],因为一个线程是读取数据,一个线程是处理数据
  • 2.创建StreamingContext两种方式,如果采用的是StreamingContext(conf,Durations.seconds(5))这种方式,不能在new SparkContext
  • 3.Durations 批次间隔时间的设置需要根据集群的资源情况以及监控每一个job的执行时间来调节出最佳时间。
  • 4.SparkStreaming所有业务处理完成之后需要有一个output operato操作
  • 5.StreamingContext.start()straming框架启动之后是不能在次添加业务逻辑
  • 6.StreamingContext.stop()无参的stop方法会将sparkContext一同关闭,stop(false) ,默认为true,会一同关闭
  • 7.StreamingContext.stop()停止之后是不能在调用start
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object StreamingTest {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
    conf.setMaster("local[2]")
    conf.setAppName("streamingTest")
    val sc = new SparkContext(conf)


    val ssc = new StreamingContext(sc,Durations.seconds(5))
//    val ssc = new StreamingContext(conf,Durations.seconds(5))  // 两种创建方式,如果使用了 conf,那么就不要再前面穿件sparkContext对象,
//    从ssc中获取SparkContext()
//    val context: SparkContext = ssc.sparkContext
    ssc.sparkContext.setLogLevel("Error")
    val lines: ReceiverInputDStream[String] = ssc.socketTextStream("node004",9999)
    val words: DStream[String] = lines.flatMap(one=>{one.split(" ")})
    val pairWords: DStream[(String, Int)] = words.map(one=>{(one,1)})
    val result: DStream[(String, Int)] = pairWords.reduceByKey((v1:Int, v2:Int)=>{v1+v2})
    result.print(100)




    ssc.start()
    ssc.awaitTermination()

//    ssc.stop(false)
  }
}
foreachRDD

foreachRDD 注意事项:

  • 1.foreachRDD中可以拿到DStream中的RDD,对RDD进行操作,但是一定要使用RDD的action算子触发执行,不然DStream的逻辑也不会执行
  • 2.froeachRDD算子内,拿到的RDD算子操作外,这段代码是在Driver端执行的,可以利用这点做到动态的改变广播变量
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object StreamingTest {
 def main(args: Array[String]): Unit = {
   val conf = new SparkConf()
   conf.setMaster("local[2]")
   conf.setAppName("streamingTest")
   val sc = new SparkContext(conf)


   val ssc = new StreamingContext(sc,Durations.seconds(5))
//    val ssc = new StreamingContext(sc,Durations.seconds(5))
   ssc.sparkContext.setLogLevel("Error")
   val lines: ReceiverInputDStream[String] = ssc.socketTextStream("node004",9999)
   val words: DStream[String] = lines.flatMap(one=>{one.split(" ")})
   val pairWords: DStream[(String, Int)] = words.map(one=>{(one,1)})
   val result: DStream[(String, Int)] = pairWords.reduceByKey((v1:Int, v2:Int)=>{v1+v2})
   result.print(100)

   result.foreachRDD(pairRDD=>{
     println("===========Driver 广播黑名单=============") //froeachRDD算子内,拿到的RDD算子操作外,这段代码是在Driver端执行的,可以利用这点做到动态的改变广播变量,这行每5s执行一次

     pairRDD.filter(one => {
       println("executor in filter ======="+one)
       true

     }).count()
//      val resultRDD: RDD[(String, Int)] = newRDD.map(one => {
//        println("executor in map *****" + one)
//        one
//      })
//      resultRDD.count()

   })


   ssc.start()
   ssc.awaitTermination()

//    ssc.stop(false)
 }
}
updateStateByKey

计算从开始到现在的所有的值

package com.scalaspark.streaming

import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}

/**
  * UpdateStateByKey 根据key更新状态
  *   1、为Spark Streaming中每一个Key维护一份state状态,state类型可以是任意类型的, 可以是一个自定义的对象,那么更新函数也可以是自定义的。
  *   2、通过更新函数对该key的状态不断更新,对于每个新的batch而言,Spark Streaming会在使用updateStateByKey的时候为已经存在的key进行state的状态更新
  */
object UpdateStateByKey {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
    conf.setMaster("local[2]")
    conf.setAppName("UpdateStateByKey")
    val ssc = new StreamingContext(conf,Durations.seconds(5))
    //设置日志级别
    ssc.sparkContext.setLogLevel("ERROR")
    val lines: ReceiverInputDStream[String] = ssc.socketTextStream("node5",9999)
    val words: DStream[String] = lines.flatMap(line=>{line.split(" ")})
    val pairWords: DStream[(String, Int)] = words.map(word => {(word, 1)})

    /**
      * 根据key更新状态,需要设置 checkpoint来保存状态
      * 默认key的状态在内存中 有一份,在checkpoint目录中有一份。
      *
      *    多久会将内存中的数据(每一个key所对应的状态)写入到磁盘上一份呢?
      * 	      如果你的batchInterval小于10s  那么10s会将内存中的数据写入到磁盘一份
      * 	      如果bacthInterval 大于10s,那么就以bacthInterval为准
      *
      *    这样做是为了防止频繁的写HDFS
      */
//    ssc.checkpoint("./data/streamingCheckpoint")
    ssc.sparkContext.setCheckpointDir("./data/streamingCheckpoint")
    /**
      * currentValues :当前批次某个 key 对应所有的value 组成的一个集合
      * preValue : 以往批次当前key 对应的总状态值
      */
    val result: DStream[(String, Int)] = pairWords.updateStateByKey((currentValues: Seq[Int], preValue: Option[Int]) => {
      var totalValues = 0
      if (!preValue.isEmpty) {
        totalValues += preValue.get
      }
      for(value <- currentValues){
        totalValues += value
      }

      Option(totalValues)
    })
    result.print()
    ssc.start()
    ssc.awaitTermination()
    ssc.stop()

  }
}

窗口操作 reduceByKeyAndWindow

每隔窗口滑动间隔时间 计算 窗口长度内的数据,按照指定的方式处理
计算每隔一段时间(a),某一段时间(b) 所获取的reduceByKey的值
例如:reduceByKeyAndWindow((v1:Int,v2:Int)=>(v1+v2),Durations.seconds(15),Durations.seconds(5))
在这里插入图片描述
假设每隔5s 1个batch,上图中窗口长度为15s,窗口滑动间隔5s
滑动间隔和窗口长度必须是 batchInterval 整数倍

package com.bjsxt.scalaspark.streaming

import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.SparkConf

/**
  * SparkStreaming 窗口操作
  * reduceByKeyAndWindow
  * 每隔窗口滑动间隔时间 计算 窗口长度内的数据,按照指定的方式处理
  */
object WindowOperator {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
    conf.setAppName("windowOperator")
    conf.setMaster("local[2]")
    val ssc = new StreamingContext(conf,Durations.seconds(5))
    ssc.sparkContext.setLogLevel("Error")
    val lines: ReceiverInputDStream[String] = ssc.socketTextStream("node5",9999)
    val words: DStream[String] = lines.flatMap(line=>{line.split(" ")})
    val pairWords: DStream[(String, Int)] = words.map(word=>{(word,1)})

//    val ds : DStream[(String,Int)] = pairWords.window(Durations.seconds(15),Durations.seconds(5))

    /**
      * 窗口操作普通的机制
      *
      * 滑动间隔和窗口长度必须是 batchInterval 整数倍
      */
    val windowResult: DStream[(String, Int)] =
      pairWords.reduceByKeyAndWindow((v1:Int, v2:Int)=>{v1+v2},Durations.seconds(15),Durations.seconds(5))


    windowResult.print()

    ssc.start()
    ssc.awaitTermination()
    ssc.stop()

  }
}

如果使用的计算每个滑动间隔内计算完成不了,我们可以进行优化,因为是在窗口长度内,都是将最前面的一个间隔去掉加入新的间隔。可以减少计算,所有可以优化,但是优化需要保留中间数据,也就是需要使用checkpoint,

    /**
      * 窗口操作优化的机制
      */
    ssc.checkpoint("./data/streamingCheckpoint")  // 使用了checkpoint
        val windowResult: DStream[(String, Int)] = pairWords.reduceByKeyAndWindow(
                            (v1:Int, v2:Int)=>{v 1+v2},
                            (v1:Int, v2:Int)=>{v1-v2},  // 此处多了一个运算
                            Durations.seconds(15),
                            Durations.seconds(5))


在这里插入图片描述
也可以自己实现

val ds : DStream[(String,Int)] = pairWords.window(Durations.seconds(15),Durations.seconds(5))
transform算子

transform 算子可以拿到DStream中的RDD,对RDD使用RDD的算子操作,但是最后要返回RDD,返回的RDD又被封装到一个DStream
transform中拿到的RDD的算子外,代码是在Driver端执行的。可以做到动态的改变广播变量

textFileStream(dir)
  • SparkStreaming 监控一个目录数据时
  • 1.这个目录下已经存在的文件不会被监控到, 可以监控增加的文件
  • 2.增加的文件必须是原子性产生。
saveAsTextFile
  • 将SparkSteaming处理的结果保存在指定的目录中

  • saveAsTextFiles(prefix, [suffix]):

  • 将此DStream的内容另存为文本文件。每批次数据产生的文件名称格式基于:prefix和suffix: “prefix-TIME_IN_MS[.suffix]”.

  • 注意:

  • saveAsTextFile是调用saveAsHadoopFile实现的

  • spark中普通rdd可以直接只用saveAsTextFile(path)的方式,保存到本地,但是此时DStream的只有saveAsTextFiles()方法,没有传入路径的方法,
    其参数只有prefix, suffix
    其实:DStream中的saveAsTextFiles方法中又调用了rdd中的saveAsTextFile方法,我们需要将path包含在prefix中

Driver HA

因为SparkStreaming是7*24小时运行,Driver只是一个简单的进程,有可能挂掉,所以实现Driver的HA就有必要(如果使用的Client模式就无法实现Driver HA ,这里针对的是cluster模式)。Yarn平台cluster模式提交任务,AM(AplicationMaster)相当于Driver,如果挂掉会自动启动AM。这里所说的DriverHA针对的是Spark standalone和Mesos资源调度的情况下。实现Driver的高可用有两个步骤:
第一:提交任务层面,在提交任务的时候加上选项 --supervise,当Driver挂掉的时候会自动重启Driver。但是重启的driver不知道原来的任务的进度和逻辑,所以要进行代码层面恢复Driver
第二:代码层面,使用JavaStreamingContext.getOrCreate(checkpoint路径,JavaStreamingContextFactory)
 Driver中元数据包括:

  1. 创建应用程序的配置信息。
  2. DStream的操作逻辑。
  3. job中没有完成的批次数据,也就是job的执行进度。
package com.scalaspark.streaming

import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Durations, StreamingContext}

/**
  * Driver HA :
  * 1.在提交application的时候  添加 --supervise 选项  如果Driver挂掉 会自动启动一个Driver
  * 2.代码层面恢复Driver(StreamingContext)
  *
  */
object SparkStreamingDriverHA {
  //设置checkpoint目录
  val ckDir = "./data/streamingCheckpoint"
  def main(args: Array[String]): Unit = {

    /**
      * StreamingContext.getOrCreate(ckDir,CreateStreamingContext)
      *   这个方法首先会从ckDir目录中获取StreamingContext【 因为StreamingContext是序列化存储在Checkpoint目录中,恢复时会尝试反序列化这些objects。
      *   如果用修改过的class可能会导致错误,此时需要更换checkpoint目录或者删除checkpoint目录中的数据,程序才能起来。】
      *
      *   若能获取回来StreamingContext,就不会执行CreateStreamingContext这个方法创建,否则就会创建
      *
      */
    val ssc: StreamingContext = StreamingContext.getOrCreate(ckDir,CreateStreamingContext)
    ssc.start()
    ssc.awaitTermination()
    ssc.stop()

  }

  def CreateStreamingContext() = {
    println("=======Create new StreamingContext =======")
    val conf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("DriverHA")
    val ssc: StreamingContext = new StreamingContext(conf,Durations.seconds(5))
    ssc.sparkContext.setLogLevel("Error")

    /**
      *   默认checkpoint 存储:
      *     1.配置信息
      *   	2.DStream操作逻辑
      *   	3.job的执行进度
      *   * 4.offset
      */
    ssc.checkpoint(ckDir)
    val lines: DStream[String] = ssc.textFileStream("./data/streamingCopyFile")
    val words: DStream[String] = lines.flatMap(line=>{line.trim.split(" ")})
    val pairWords: DStream[(String, Int)] = words.map(word=>{(word,1)})
    val result: DStream[(String, Int)] = pairWords.reduceByKey((v1:Int, v2:Int)=>{v1+v2})

//    result.print()

    /**
      * 更改逻辑,从ckdir中恢复driver,要是能恢复,新逻辑不执行,旧逻辑执行
      */
    result.foreachRDD(pairRDD=>{
      pairRDD.filter(one=>{
        println("*********** filter *********")
        true
      })
      pairRDD.foreach(println)
    })

    ssc
  }
}

checkpoint会存储

  • 配置信息
  • DStream操作逻辑
  • job的进度
  • offset (kafka消息的位置)

目前只能 从 ckdir 中恢复StreamContext,就算代码更改了也还是会按照原来的代码逻辑执行。
除非更换 ckdir 目录或者清空ckdir目录,但是更换或者清空ckdir目录就无法恢复到原来driver执行任务的具体细节
driver HA 主要用在 当停止sparkStreaming时,再次启动,sparkStreaming可以接着上次消费的数据继续消费

如果我们想要既恢复dirver,又想改变代码逻辑,我们就要手动管理foffset,不让spark自己管理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值