SparkStreaming

Transformation:

​ 和RDD的类似,有几个需要单独学习: transform、UpdateStateByKey、Window Operations

Output/Action:

​ 和RDD的类似,有几个需要单独学习:print、foreachRDD(func)

UpdateStateByKey

package cn.itcast.spark.stream

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

/**
  * Author itcast
  * Date 2019/9/24 10:39
  * Desc 演示使用SparkStreaming接收来自Socket的数据(ip:node01,port:9999),并对所有数据进行聚合,使用updateStateByKey
  */
object WordCount2 {
  def main(args: Array[String]): Unit = {
    //1.创建StreamingContext
    //spark.master should be set as local[n], n > 1 in local mode
    val conf: SparkConf = new SparkConf().setAppName("wc").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    sc.setLogLevel("WARN")
    val ssc = new StreamingContext(sc, Seconds(5))//Seconds(5)表示每隔5秒对数据进行批次的划分
    //The checkpoint directory has not been set. Please set it by StreamingContext.checkpoint().
    //注意:根据错误提示需要设置一个checkpoint目录,因为我们要对历史值进行聚合,历史值存在哪里?需要给它设置一个目录
    ssc.checkpoint("./ssc") //实际开发写HDFS
    //2.使用ssc从Socket接收数据
    val dataDStream: ReceiverInputDStream[String] = ssc.socketTextStream("node01",9999)
    //3.对数据进行处理
    //3.1切分
    val wordDStream: DStream[String] = dataDStream.flatMap(_.split(" "))//_表示发送过来的每一行数据
    //3.2每个单词记为1
    val wordAndOneDStream: DStream[(String, Int)] = wordDStream.map((_, 1))//_表示每个单词
    //3.3聚合--对所有数据进行聚合,使用updateStateByKey
    //val result: DStream[(String, Int)] = wordAndOneDStream.reduceByKey(_+_)
    //updateFunc: (Seq[V], Option[S]) => Option[S]
    val result: DStream[(String, Int)] = wordAndOneDStream.updateStateByKey(updateFunc)
    //3.4输出结果
    result.print()

    ssc.start()//开启
    ssc.awaitTermination()//等待终止
  }

  /**
    * 定义一个updateFunc方法用来聚合所有的数据(将历史值+当前这一批次的值)
    * @param currentValues 当前这一批次的值[1,1,1,.....]
    * @param historyValue 历史值,第一次没有为None,我们应该把它设置为0,所以应该用getOrElse(0)
    * @return
    */
  def updateFunc(currentValues:Seq[Int], historyValue:Option[Int]):Option[Int]={
    val result: Int = historyValue.getOrElse(0) + currentValues.sum
    Some(result)
  }
}

reduceByKeyAndWindows

package cn.itcast.spark.stream

import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
/**
  * Author itcast
  * Date 2019/9/24 10:39
  * Desc 演示使用SparkStreaming接收来自Socket的数据(ip:node01,port:9999),
  * 使用窗口操作,统计最近10s的数据,每隔5s统计一次
  */
object WordCount3 {
  def main(args: Array[String]): Unit = {
    //1.创建StreamingContext
    //spark.master should be set as local[n], n > 1 in local mode
    val conf: SparkConf = new SparkConf().setAppName("wc").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    sc.setLogLevel("WARN")
    val ssc = new StreamingContext(sc, Seconds(5))//Seconds(5)表示每隔5秒对数据进行批次的划分
    //The checkpoint directory has not been set. Please set it by StreamingContext.checkpoint().
    //注意:根据错误提示需要设置一个checkpoint目录,因为我们要对历史值进行聚合,历史值存在哪里?需要给它设置一个目录
    ssc.checkpoint("./ssc") //实际开发写HDFS
    //2.使用ssc从Socket接收数据
    val dataDStream: ReceiverInputDStream[String] = ssc.socketTextStream("node01",9999)
    //3.对数据进行处理
    //3.1切分
    val wordDStream: DStream[String] = dataDStream.flatMap(_.split(" "))//_表示发送过来的每一行数据
    //3.2每个单词记为1
    val wordAndOneDStream: DStream[(String, Int)] = wordDStream.map((_, 1))//_表示每个单词
    //3.3聚合--对所有数据进行聚合,使用updateStateByKey
    //val result: DStream[(String, Int)] = wordAndOneDStream.reduceByKey(_+_)
    //updateFunc: (Seq[V], Option[S]) => Option[S]
    //val result: DStream[(String, Int)] = wordAndOneDStream.updateStateByKey(updateFunc)
    //=====================================================================
    //使用窗口操作,统计最近10s的数据,每隔5s统计一次
    // @param windowDuration 窗口的长度  10s
    // width of the window; must be a multiple of this DStream's batching interval
    // @param slideDuration 滑动间隔   5s
    // sliding interval of the window (i.e., the interval after which the new DStream will generate RDDs); must be a multiple of this DStream's batching interval
    //windowDuration>slideDuration 部分数据会被重复(重新)计算,实际中用的最多
    val result: DStream[(String, Int)] = wordAndOneDStream.reduceByKeyAndWindow((a:Int,b:Int)=>a+b,Seconds(10),Seconds(5))
    //windowDuration=slideDuration 数据不会被重新计算也不会丢失,实际中偶尔会用
    //windowDuration(10)<slideDuration(15) 部分数据会丢失不参与计算,实际中基本不会使用
    //这里需要注意:以后开发中需要学会设置windowDuration和slideDuration这两个参数:
    //如:广告流点击预测,统计最近1小时的广告点击数据,每隔1分钟更新一次
    //那么windowDuration = Seconds(3600) , slideDuration=Seconds(60)
    //或者windowDuration = Minutes(60) , slideDuration=Minutes(1)

    //3.4输出结果
    result.print()

    ssc.start()//开启
    ssc.awaitTermination()//等待终止
  }
}

模拟百度热搜求topN

package cn.itcast.spark.stream

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

/**
  * Author itcast
  * Date 2019/9/24 10:39
  * Desc 演示使用SparkStreaming接收来自Socket的数据(ip:node01,port:9999),
  * 使用窗口操作,模拟百度热搜词汇排行榜TopN
  * 统计最近10s的数据,每隔5s统计一次
  */
object WordCount4 {
  def main(args: Array[String]): Unit = {
    //1.创建StreamingContext
    //spark.master should be set as local[n], n > 1 in local mode
    val conf: SparkConf = new SparkConf().setAppName("wc").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    sc.setLogLevel("WARN")
    val ssc = new StreamingContext(sc, Seconds(5))//Seconds(5)表示每隔5秒对数据进行批次的划分
    //The checkpoint directory has not been set. Please set it by StreamingContext.checkpoint().
    //注意:根据错误提示需要设置一个checkpoint目录,因为我们要对历史值进行聚合,历史值存在哪里?需要给它设置一个目录
    ssc.checkpoint("./ssc") //实际开发写HDFS
    //2.使用ssc从Socket接收数据
    val dataDStream: ReceiverInputDStream[String] = ssc.socketTextStream("node01",9999)
    //3.对数据进行处理
    //3.1切分
    val wordDStream: DStream[String] = dataDStream.flatMap(_.split(" "))//_表示发送过来的每一行数据
    //3.2每个单词记为1
    val wordAndOneDStream: DStream[(String, Int)] = wordDStream.map((_, 1))//_表示每个单词
    //4.统计最近10s的数据,每隔5s统计一次
    // windowDuration:10s
    // slideDuration: 5s
    val wordAndCountDStream: DStream[(String, Int)] = wordAndOneDStream.reduceByKeyAndWindow((a:Int,b:Int)=>a+b,Seconds(10),Seconds(5))
    //5.求TopN
    val result: DStream[(String, Int)] = wordAndCountDStream.transform(rdd => {
      //5.1排序
      //_表示(单词, 数量) , _._2表示按照数量排序 ,false表示逆序
      val sortRDD: Array[(String, Int)] = rdd.sortBy(_._2, false).take(3)
      println("==========top3========")
      sortRDD.foreach(println)
      println("==========top3========")
      rdd
    })
    //输出结果
    //注意:上面的代码没有涉及到DStream的Action操作,会报错:No output operations registered, so nothing to execute
    //所以下面的代码不能省略
    result.print()

    ssc.start()//开启
    ssc.awaitTermination()//等待终止
  }
}

使用SpartStreaming整合kafka

在这里插入图片描述

1.★面试题:Receiver & Direct

开发中我们经常会利用SparkStreaming实时地读取kafka中的数据然后进行处理,在spark1.3版本后,kafkaUtils里面提供了两种创建DStream的方法:

1.Receiver接收方式:

KafkaUtils.createDstream(开发中不用,了解即可,但是面试可能会问)

Receiver作为常驻的Task运行在Executor等待数据,但是一个Receiver效率低,需要开启多个,再手动合并数据(union),再进行处理,很麻烦

Receiver那台机器挂了,可能会丢失数据,所以需要开启WAL(预写日志)保证数据安全,那么效率又会降低!

Receiver方式是通过zookeeper来连接kafka队列,调用Kafka高阶API,offset存储在zookeeper,由Receiver维护,

spark在消费的时候为了保证数据不丢也会在Checkpoint中存一份offset,可能会出现数据不一致

所以不管从何种角度来说,Receiver模式都不适合在开发中使用了,已经淘汰了

2.Direct直连方式:

KafkaUtils.createDirectStream(开发中使用,要求掌握)

Direct方式是直接连接kafka分区来获取数据,从每个分区直接读取数据大大提高了并行能力

Direct方式调用Kafka低阶API(底层API),offset自己存储和维护,默认由Spark维护在checkpoint中,消除了与zk不一致的情况

当然也可以自己手动维护,把offset存在mysql、redis中

所以基于Direct模式可以在开发中使用,且借助Direct模式的特点+手动操作可以保证数据的Exactly once 精准一次

package cn.itcast.spark.stream

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
  * Author itcast
  * Date 2019/9/24 16:11
  * Desc 使用spark-streaming-kafka-0-10_2.11版本的Direct模式整合Kafka
  */
object SparkStreamingKafkaDemo {
  def main(args: Array[String]): Unit = {
    //1.准备环境
    val conf: SparkConf = new SparkConf().setAppName("wc").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    sc.setLogLevel("WARN")
    val ssc = new StreamingContext(sc, Seconds(5))//Seconds(5)表示每隔5秒对数据进行批次的划分

    //2.准备参数
    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "node01:9092,node02:9092,node03:9092,",//Kafka集群地址
      "key.deserializer" -> classOf[StringDeserializer], //key的反序列化类型
      "value.deserializer" -> classOf[StringDeserializer],//value的反序列化类型
      "group.id" -> "sparkstreaming", //消费者组名称
      //earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
      //latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
      //none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
      //这里配置latest自动重置偏移量为最新的偏移量,即如果有偏移量从偏移量位置开始消费,没有偏移量从新来的数据开始消费
      "auto.offset.reset" -> "latest",
      "enable.auto.commit" -> (false: java.lang.Boolean)//false表示不开启自动提交zk/__consumer_offset中,默认由Spark手动提交到Checkpoint中或程序员手动提交
    )
    //3.主题
    val topics = Array("spark_kafka")

    //4.连接Kafka
    //ssc: StreamingContext,
    //locationStrategy: LocationStrategy,
    //consumerStrategy: ConsumerStrategy[K, V],
    val recordDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](ssc,
      LocationStrategies.PreferConsistent,//位置策略,直接使用源码中强烈推荐的PreferConsistent
      ConsumerStrategies.Subscribe[String, String](topics, kafkaParams)) //消费策略:直接使用源码中强烈推荐的Subscribe

    //5.处理数据--获取value
    val lineDStream: DStream[String] = recordDStream.map(_.value())//_表示从Kakfa消费到的每一条完整的消息(里面包含key/value/offset/topic)

    //6.WordCount
    val result: DStream[(String, Int)] = lineDStream.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)

    result.print()

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

SparkStreaming整合kafka的面试题

1. 如何保证消息不丢失?

ü 生产者:消息确认机制ACK

0:不需要确认,消费可能丢失

1:Leader确认即可,消息一般来说不会丢失

-1:Leader和Follower都确认,消息绝对安全,但是效率低

ü 消费者:维护偏移量offset

先提交再消费:可能会出现offset提交了,但没有消费消息,程序这时挂了,则消息丢失

先消费再提交:可能会出现消息消费了,但没有提交,程序挂了,重启后会出现重复消费

2. 如何保证消息不被重复消费?

出现概率较低且一般业务如果对重复数据不敏感,则不需要处理(也可以在后期进行数据清洗时再处理)

但是涉及到钱相关的,或者其他对于数据要求精确性高的,则需要避免消息重复

可以在真正消费处理这个消息的时候,把消息的key存在外部容器中(如Redis),下次消费的时候,容器中有,则表示消费过了,就不再消费
3. 如何保证消息消费有序?

一个分区内部的消息本身就是有序的,但是不能保证全部分区的消息读取有序,即全局无序,区内有序

如果要保证消息消费有序,则只能有一个分区,但是就违背了kakfa通过分区提高并发的初衷

所以这个命题没有意义,仅仅是面试官难为你

4. Kafka为什么能够吞吐量那么高?

本身从软件架构设计上就是为高并发高吞吐所设计的:多broker/多分区/消息消费拉模式…

底层:写磁盘文件的时候顺序读写(Sequence IO)、页缓存(PageCache)、零拷贝机制(Sendfile)…

总之不管从软件架构上,还是从物理硬件原理上,Kafka都是一个天生为高并发、高吞吐所设计的!

**5. 读取消息时为什么只从Leader上去读?不从Follower上去读? **

因为kafka并发读已经由分区来实现了,如果还要保证一条消息写入到Leader后,能从Follower进行并发读,那么则需要保证Leader和Follower数据的实时一致性,这样写消息就慢了!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值