Spark Streaming超神学习05

1.流批对比

在这里插入图片描述

Spark Streaming类似于Apache Storm,用于流式数据的处理。根据其官方文档介绍,Spark Streaming有高吞吐量和容错能力强等特点.

2.输入位置和输出位置

在这里插入图片描述

和Spark基于RDD的概念很相似,Spark Streaming使用离散化流(discretized stream)作为抽象表示,叫作DStream。DStream 是随时间推移而收到的数据的序列。在内部,每个时间区间收到的数据都作为 RDD 存在,而 DStream 是由这些 RDD 所组成的序列(因此 得名“离散化”)。

在这里插入图片描述
Spark Streaming的编程抽象是离散化流,也就是DStream。它是一个 RDD 序列,每个RDD代表数据流中一个时间片内的数据。

在这里插入图片描述

3.基于receiver的streaming作业执行流程

在这里插入图片描述

4.什么是DStreams

Discretized Stream是Spark Streaming的基础抽象,代表持续性的数据流和经过各种Spark原语操作后的结果数据流。在内部实现上,DStream是一系列连续的RDD来表示。每个RDD含有一段时间间隔内的数据,
Spark Streaming把一系列连续的批次称为Dstream。

5.Dstream的特点:

就是将流式计算分解成为一系列确定并且较小的批处理作业
可以将失败或者执行较慢的任务在其他节点上并行执行
有较强的的容错能力,基于lineage
Dstream内含high-level operations进行处理
Dstream内部实现为一个RDD序列
在这里插入图片描述
在这里插入图片描述

6.DStreams输入

Spark Streaming原生支持一些不同的数据源。一些“核心”数据源已经被打包到Spark Streaming 的 Maven 工件中,而其他的一些则可以通过 spark-streaming-kafka 等附加工件获取。

Steaming的数据源和接收器

数据源:基本数据源/高级数据源
基本数据源:socket、file,akka actoer。Steaming中自带了该数据源的读取API
高级数据源:kafka,flume,kinesis,Twitter等其他的数据。必须单独导入集成的JAR包spark-streaming-[projectname]_2.10

接收器:

Socket数据源,有一个接收器,用户接受Socket的数据。每一个接收器必须占用一个cores来接收数据。如果资源不足,那么任务就会处于等待状态。

CPU 核心:

每个接收器都以 Spark 执行器程序中一个长期运行的任务的形式运行,因此会占据分配给应用的 CPU 核心。此外,我们还需要有可用的 CPU 核心来处理数据。这意味着如果要运行多个接收器,就必须至少有和接收器数目相同的核心数,还要加上用来完成计算所需要的核心数。例如,如果我们想要在流计算应用中运行 10 个接收器,那么至少需要为应用分配 11 个 CPU 核心。所以如果在本地模式运行,不要使用local或者local[1]。

7.Receiver读取kafka数据特点

在spark的executor中,启动一个接收器,专门用于读取kafka的数据,然后存入到内存中,供sparkStreaming消费
1、为了保证数据0丢失,WAL,数据会保存2份,有冗余
2、Receiver是单点读数据,如果挂掉,程序不能运行
3、数据读到executor内存中,增大了内存使用的压力,如果消费不及时,会造成数据积压
如下图:
在这里插入图片描述

还有几个需要注意的点:

1、Kafka中topic的partition与Spark Streaming中生成的RDD的partition无关,因此,在KafkaUtils.createStream()中,增加某个topic的partition的数量,只会增加单个Receiver消费topic的线程数,也就是读取Kafka中topic partition的线程数量,它不会增加Spark在处理数据时的并行性。
2、可以使用不同的consumer group和topic创建多个Kafka输入DStream,以使用多个receiver并行接收数据。
3、如果已使用HDFS等复制文件系统启用了“预读日志”,则接收的数据已在日志中复制。因此,输入流的存储级别的存储级别StorageLevel.MEMORY_AND_DISK_SER(即,使用KafkaUtils.createStream(…, StorageLevel.MEMORY_AND_DISK_SER))。

8.Direct方式

Direct:直连模式,在spark1.3之后,引入了Direct方式。不同于Receiver的方式,Direct方式没有receiver这一层,其会周期性的获取Kafka中每个topic的每个partition中的最新offsets,并且相应的定义要在每个batch中处理偏移范围,当启动处理数据的作业时,kafka的简单的消费者api用于从kafka读取定义的偏移范围 。其形式如下图:

在这里插入图片描述
这种方法相较于Receiver方式的优势在于:

1、简化的并行:在Receiver的方式中我们提到创建多个Receiver之后利用union来合并成一个Dstream的方式提高数据传输并行度。而在Direct方式中,Kafka中的partition与RDD中的partition是一一对应的并行读取Kafka数据,这种映射关系也更利于理解和优化。
2、高效:在Receiver的方式中,为了达到0数据丢失需要将数据存入Write Ahead Log中,这样在Kafka和日志中就保存了两份数据,浪费!而第二种方式不存在这个问题,只要我们Kafka的数据保留时间足够长,我们都能够从Kafka进行数据恢复。
3、精确一次:在Receiver的方式中,使用的是Kafka的高阶API接口从Zookeeper中获取offset值,这也是传统的从Kafka中读取数据的方式,但由于Spark Streaming消费的数据和Zookeeper中记录的offset不同步,这种方式偶尔会造成数据重复消费。而第二种方式,直接使用了简单的低阶Kafka API,Offsets则利用Spark Streaming的checkpoints进行记录,消除了这种不一致性。
请注意,此方法的一个缺点是它不会更新Zookeeper中的偏移量,因此基于Zookeeper的Kafka监视工具将不会显示进度。但是,您可以在每个批处理中访问此方法处理的偏移量,并自行更新Zookeeper。

直连模式特点:tatch time 每隔一段时间,去kafka读取一批数据,然后消费
简化并行度,rdd的分区数量=topic的分区数量
数据存储于kafka中,没有数据冗余
不存在单点问题
效率高
可以实现仅消费一次的语义 exactly-once语义

代码入门

socket模拟实时不间断接收数据

/**
 * SparkStreaming的入门案例
 *      通过网络socket来模拟实时不断产生数据并处理
 *  linux的安装:
 *      yum -y installl nc
 *      nc -lk 9999启动端口发送数据
 *  StreamingContext剖析:
 *      local       :是当前程序分配一个工作线程
 *      local[*]    :给当前程序分配可以用工作线程(通常会>=2)
 *   这个改动造成程序只接收数据,而不进行处理,那么也就意味着,streaming需要使用线程资源既要接收数据,还有消费数据,而且优先接收数据,接收数据要独占一个线程字段。
 *
 *   在本地模式下面,去处理receiver到的数据的时候,线程资源最好设置大于1。
 *
 */
object Demo1 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("Demo1").setMaster("local[*]")
    val batchDuration = Seconds(2)//每2s提交一次sparkstreaming的作业
    val sc = new StreamingContext(conf,batchDuration)
    //加载外部数据
    val value:ReceiverInputDStream[String] = sc.socketTextStream("hadoop003", 9999, storageLevel = StorageLevel.MEMORY_AND_DISK_SER_2)
    val ds:DStream[String] = value.flatMap(line => line.split("\\s+"))
    val ds1:DStream[(String,Int)] = ds.map(word => (word, 1))
    val ds2:DStream[(String,Int)] = ds1.reduceByKey(_ + _)
    ds2.print()
    /*
            start操作,是用来启动streaming程序的计算,如果不执行start操作,程序压根儿不会执行
         */
    sc.start()
    /*
     Adding new inputs, transformations, and output operations after starting a context is not supported
     在程序启动之后,也就是start之后,不可以再添加任何的业务逻辑
  */
    //        ret.print()
    /*
           如果没有awaitTermination方法,程序不会持续不断的运行,说白就是把driver设置成为一个后台的守护线程
        */
    sc.awaitTermination()
  }
}

从hdfs读取数据

/**
 *  从hdfs读取数据:
 *      streaming只能做到读取一个目录下的新增文件,不能做到读取文件中的新增数据
 */
object Demo2 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setMaster("local")//因为这次读数据返回是DStream,不是ReceiverInputDStream可以少一个线程
      .setAppName("_01StreamingNetWorkOps")
    //两次流式计算之间的时间间隔,batchInterval
    val batchDuration = Seconds(2) // 每隔2s提交一次sparkstreaming的作业
    val ssc = new StreamingContext(conf, batchDuration)

    //加载外部数据
    val input:DStream[String] = ssc.textFileStream("hdfs://hadoop003:9000/test/spark")
    val words: DStream[String] = input.flatMap(line => line.split("\\s+"))
    val pairs:DStream[(String, Int)] = words.map(word => (word, 1))
    val ret = pairs.reduceByKey(_+_)
    ret.print()


    ssc.start()
    ssc.awaitTermination()
  }
}

SparkStremaing从kafka中读取数据

/*
    SparkStremaing从kafka中读取数据
  PerPartitionConfig
    spark.streaming.kafka.maxRatePerPartition
    spark.streaming.kafka.minRatePerPartition
            都是代表了streaming程序消费kafka速率,
                max: 每秒钟从每个分区读取的最大的纪录条数
                    max=10,分区个数为3,间隔时间为2s
                        所以这一个批次能够读到的最大的纪录条数就是:10*3*2=60
                        如果配置的为0,或者不设置,起速率没有上限
                min: 每秒钟从每个分区读取的最小的纪录条数
              那么也就意味着,streaming从kafka中读取数据的速率就在[min, max]之间
    执行过程中出现序列化问题:
        serializable (class: org.apache.kafka.clients.consumer.ConsumerRecord, value: ConsumerRecord
        spark中有两种序列化的方式
            默认的就是java序列化的方式,也就是写一个类 implement Serializable接口,这种方式的优点是非常稳定,但是一个缺点是性能不佳
            还有一种高性能的序列化方式——kryo的序列化,性能非常高,官方给出的数据是超过java的序列化性能10倍,同时在使用的时候只需要做一个声明式的注册即可
                sparkConf.set("spark.serializer", classOf[KryoSerializer].getName)//指定序列化的方式
                    .registerKryoClasses(Array(classOf[ConsumerRecord[String, String]]))//注册要序列化的类

 */
object Demo3 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setMaster("local")
      .setAppName("Demo3")
    //            .set("spark.serializer", classOf[KryoSerializer].getName)
    //            .registerKryoClasses(Array(classOf[ConsumerRecord[String, String]]))
    //两次流式计算之间的时间间隔,batchInterval
    val batchDuration = Seconds(2) // 每隔2s提交一次sparkstreaming的作业
    val ssc = new StreamingContext(conf, batchDuration)


    val topics = Set("hadoop")
    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "hadoop003:9092,hadoop004:9092,hadoop005:9092",
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "spark-kafka-group-0817",
      "auto.offset.reset" -> "earliest",
      "enable.auto.commit" -> "false"
    )

    //0 449 1 449 2 445

    /*
        从kafka中读取数据
        locationStrategy:位置策略
            制定如何去给特定的topic和partition来分配消费者进行调度,可以通过LocationStrategies来得到实例。
            在kafka0.10之后消费者先拉取数据,所以在适当的executor来缓存executor操作对于提高性能是非常重要的。
            PreferBrokers:
                如果你的executor在kafka的broker实例在相同的节点之上可以使用这种方式。
            PreferConsistent:
                大多数情况下使用这个策略,会把partition分散给所有的executor
            PreferFixed:
                当网络或者设备性能等个方便不均衡的时候,可以蚕蛹这种方式给特定executor来配置特定partition。
                不在这个map映射中的partition使用PreferConsistent策略
        consumerStrategy:消费策略
            配置在driver或者在executor上面创建的kafka的消费者。该接口封装了消费者进程信息和相关的checkpoint数据
            消费者订阅的时候的策略:
                Subscribe           : 订阅多个topic进行消费,这多个topic用集合封装
                SubscribePattern    : 可以通过正则匹配的方式,来订阅多个消费者,比如订阅的topic有 aaa,aab,aac,adc,可以通过a[abc](2)来表示
                Assign              :  指定消费特定topic的partition来进行消费,是更加细粒度的策略
     */
    val message:InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream(ssc, LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe(topics, kafkaParams))
    //        message.print()//直接打印有序列化问题

    message.foreachRDD((rdd, bTime) => {
      if(!rdd.isEmpty()) {
        println("-------------------------------------------")
        println(s"Time: $bTime")
        println("-------------------------------------------")
        rdd.foreach(record => {
          println(record)
        })
      }
    })


    ssc.start()
    ssc.awaitTermination()
  }
}

kafka从指定的offset位置来读取数据

/*
    SparkStremaing从kafka中读取数据
        从指定的offset位置来读取数据
        这样就可以重复读取数据
 */
object Demo4 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setMaster("local")
      .setAppName("Demo4")
    //两次流式计算之间的时间间隔,batchInterval
    val batchDuration = Seconds(2) // 每隔2s提交一次sparkstreaming的作业
    val ssc = new StreamingContext(conf, batchDuration)


    val topics = Set("hadoop")
    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "hadoop003:9092,hadoop004:9092,hadoop005:9092",
      "key.deserializer" -> classOf[StringDeserializer],//key序列化
      "value.deserializer" -> //value序列化classOf[StringDeserializer],
      "group.id" -> "spark-kafka-group-0817",//用于标识这个消费者属于哪个消费团体
      "auto.offset.reset" -> "earliest",//偏移量 latest自动重置偏移量为最新的偏移量
      "enable.auto.commit" -> "false"//消费完不更新offset
    )

    //初始偏移量
    val offsets = Map[TopicPartition, Long](
      new TopicPartition("hadoop", 0) -> 451,
      new TopicPartition("hadoop", 1) -> 452,
      new TopicPartition("hadoop", 2) -> 447
    )
    val message:InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream(ssc, LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe(topics, kafkaParams, offsets))
    //        message.print()//直接打印有序列化问题

    message.foreachRDD((rdd, bTime) => {
      if(!rdd.isEmpty()) {
        println("-------------------------------------------")
        println(s"Time: $bTime")
        println("-------------------------------------------")
        rdd.foreach(record => {
          println(record)
        })
      }
    })


    ssc.start()
    ssc.awaitTermination()
  }
}

updateStateByKey算子

/**
 * DStream的updateStateByKey
 *  这个是一个状态算子,
 *      状态:说白了就是key所对应的value。
 *      state operator      : 计算当前key对应value,不仅仅基于当前的纪录,还得要看历史的纪录
 *          统计截止到目前为止某一个key出现的次数
 *      none state operator : 计算当前key对应value,仅仅基于当前的纪录
 *
 *      在sparkstreaming中状态算子涉及的的不多:updateStateByKey,xxxxWindow操作
 *      其他都是非状态算子,比如:map,flatMap,filter
 * updateStateByKey(updateFunc)
 *      按照key,来更新value的状态,所以key所对应的value,可能存在,可能不存在(当前key第一次出现的时候,其历史的value就不存在)
 *      统计截止到目前为止某一个key出现的次数
 *     该算子需要一个状态更新函数,来更新key对应的value,该函数需要至少两个参数
 *     updateFunc(currentValue:Seq[T], historyValue:Option[T])
 *
 * 为了持久化保存历史的状态,那么需要一个checkpoint存储目录来管理历史的状态值,在内存是相对不安全的。
 */
object Demo1 {

    def main(args: Array[String]): Unit = {
      val conf = new SparkConf()
        .setMaster("local[*]")
        .setAppName("Demo1")
      //两次流式计算之间的时间间隔,batchInterval
      val batchDuration = Seconds(2) // 每隔2s提交一次sparkstreaming的作业
      val sc = new StreamingContext(conf, batchDuration)
      //持久化保存历史值
      sc.checkpoint("C:\\Users\\70201\\Desktop\\test\\checkpoint")
      val value: ReceiverInputDStream[String] = sc.socketTextStream("hadoop003", 9999)
      val value1 = value.flatMap(line => line.split("\\s+"))
      val value2 = value1.map(num => (num, 1))
      //使用状态算子,需要传入函数
      val value3 = value2.updateStateByKey(updateFunc)
      value3.print()
      sc.start()
      sc.awaitTermination()
    }
  //实现key的value根据当前value和历史value更新值的函数
  //当前key可能有多个,多以用seq集合类型
  def updateFunc(current:Seq[Int], history:Option[Int]):Option[Int]={
    Option(current.sum+history.getOrElse(0))
  }
}

transform算子

/*
    transform,是一个transformation操作
        transform(p:(RDD[A]) => RDD[B]):DStream[B]
     类似的操作
        foreachRDD(p: (RDD[A]) => Unit)
     transform的一个非常重要的一个操作,就是来构建DStream中没有的操作,DStream的大多数操作都可以用transform来模拟
     比如map(p: (A) => B) ---> transform(rdd => rdd.map(p: (A) => B))

   最重要的操作:
      dstream.join(rdd)

   在线黑名单过滤
        一个离线的数据集RDD和一个在线的数据集DStream之间的join操作*/
/**
 * 黑名单
 *27.19.74.143
 *110.52.250.126
 *8.35.201.164
 * 传入数据
 *8.35.201.161##2016-05-30 17:38:21##GET /static/image/common/search.png HTTP/1.1##200##3047
 */
object Demo2 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setMaster("local[*]")
      .setAppName("Demo1")
    //两次流式计算之间的时间间隔,batchInterval
    val batchDuration = Seconds(2) // 每隔2s提交一次sparkstreaming的作业
    val sc = new StreamingContext(conf, batchDuration)
    val black:RDD[(String,Int)] = sc.sparkContext.parallelize(List(
      "27.19.74.143" -> 1,
      "110.52.250.126" -> 1,
      "8.35.201.164" -> 0
    ))
    val lines:ReceiverInputDStream[String]= sc.socketTextStream("hadoop003", 9999)
    val value: DStream[(String,String)] = lines.map(line => {
      val index = line.indexOf('#')
      val ip = line.substring(0, index)
      val info = line.substring(index + 2)
      (ip, info)
    })
    //黑名单过滤操作 因为dstream的join需要的数据类型是dstream,所以二者是无法直接进行join的,需要用transform转换
    val write:DStream[(String,String)] = value.transform(rdd => {
      //过滤出没有在黑名单中或者解禁的
      val join: RDD[(String, (String, Option[Int]))] = rdd.leftOuterJoin(black).filter { case (ip, (info, flag)) => {
        flag.isEmpty || flag.get == 0
      }
      }
      //返回ip,和info
      join.map { case (ip, (info, flag)) => (ip, info) }

    })
    write.print()
    sc.start()
    sc.awaitTermination()
  }
}

window操作

/**
 * window窗口操作
 *    流式无界,所以我们要向进行全局的统计肯定是行不通的,那么我们可以对这个无界的数据集进行切分,
 *    被切分的这每一个小的区间,我们可以理解为window,
 *    而sparkstreaming是准实时流计算,微小的批次操作,可以理解为是一个特殊的window窗口操作。
 *
 * 理论上,对这个窗口window的划分,有两种情况,一种就是按照数据的条数,另外一种就是按照时间。
 * 但是在sparkstreaming中目前仅支持后者,也就是说仅支持基于时间的窗口,需要提供两个参数
 * 一个参数是窗口的长度:window_length
 * 另外一个参数是窗口的计算频率:sliding_interval ,每隔多长时间计算一次window操作
 *
 * streaming程序中还有一个interval是batchInterval,那这两个interval有什么关系?
 * batchInterval,每隔多长时间启动一个spark作业,而是每隔多长时间为程序提交一批数据
 *
 * 特别需要注意的是:
 *  window_length和sliding_interval都必须是batchInterval的整数倍。
 *
 *  总结:
 *      window操作
 *          每隔M长的时间,去统计N长时间内产生的数据
 *          M被称之sliding_interval,窗口的滑动频率
 *          N被称之window_length,窗口的长度
 *     该window窗口是一个滑动的窗口。
 *
 *   当sliding_interval > window_length的时候,会出现窗口的空隙
 *   当sliding_interval < window_length的时候,会出现窗口的重合
 *   当sliding_interval = window_length的时候,两个窗口是严丝合缝的
 *
 *   batchInterval=2s
 *   sliding_interval=4s
 *   window_length=6s
 */
object Demo3 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setMaster("local[*]")
      .setAppName("Demo3")
    //两次流式计算之间的时间间隔,batchInterval
    val batchDuration = Seconds(2) // 每隔2s提交一次sparkstreaming的作业
    val ssc = new StreamingContext(conf, batchDuration)

    val lines = ssc.socketTextStream("hadoop003", 9999)
    val words = lines.flatMap(line => line.split("\\s+"))
    val pairs = words.map(word => (word, 1))
    val ret = pairs.reduceByKeyAndWindow(_+_, windowDuration = Seconds(6), slideDuration = Seconds(4))

    ret.print

    ssc.start()
    ssc.awaitTermination()
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值