Spark的认识(四)

1、本文内容

  • 1、掌握SparkStreaming底层原理
  • 2、掌握Dstream常用操作
  • 3、掌握SparkStreaming整合flume
  • 4、掌握SparkStreaming整合kafka

2、SparkStreaming概述

2.1 什么是sparkStreaming

  • Spark Streaming makes it easy to build scalable fault-tolerant streaming applications.
  • SparkStreaming是可以非常容易的构建一个可扩展、具有容错机制的流式应用程序

2.2 sparkStreaming特性

  • 1、易用性
  • 可以像开发离线批处理一样去编写流式处理程序,还是可以使用java、scala、Python等不同的语言开发
  • 2、容错性
  • sparkStreaming可以实现恰好一次语义(数据被处理且只被处理一次)
  • sparkStreaming可以实现在不需要额外代码的情况下,来实现丢失的job
  • 3、可以融合到spark生态系统
  • sparkstreaming可以和离线批处理和交互式查询相结合

3、sparkStreaming原理

3.1 sparkStreaming计算原理


Spark Streaming 是基于spark的流式批处理引擎,其基本原理是把输入数据以某一时间间隔批量的处理,当批处理间隔缩短到秒级时,便可以用于处理实时数据流。


3.2 sparkStreaming计算流程


sparkStreaming是以某一个时间间隔的批处理,按照批处理的时间间隔进行划分,划分出了很多个短小的批处理作业,每一个段数据在这里就是一个Dstream,Dstream内部是由当前该批次的数据,内部的数据是通过rdd进行封装,也就是说Dstream中封装了rdd,rdd里面有很多个分区,分区里面才是真正的数据。 后期Dstream做大量的transformation操作,最终都转换成了对它内部的rdd也就是对应的transformation。


3.3 sparkStreaming容错性


Dstream中内部封装了rdd,这个时候后期对它做大量操作的时候,如果某个rdd的分区数据丢失了,可以通过rdd的血统进行重新计算恢复得到。 通过血统在进行重新计算恢复得到丢失的数据的时候需要一定条件:就是保证数据源端安全性 sc.textFile("/words.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect 恢复数据: 血统+原始的数据进行恢复


3.4 sparkStreaming的实时性


storm是来一条数据就处理一条,实时性比较高 sparkStreamning是以某一时间间隔处理,延迟比较高,实时性比较低 后期再处理一些实时的应用的场景,具体使用哪一种框架去实现,一定要结合当前这个业务需求: 比如说 公司领导对实时性要求比较高,从数据生成到看到结果数据需要非常快的时间,这个时候优先考虑storm框架 公司领导对实时性要求不是特别高,从数据生成到看到结果数据可以允许有一定的延迟,这个时候就可以考虑使用sparkStreaming框架。


4、DStream

4.1 什么是Dstream


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


4.2 Dstream操作分类

  • 1、transformation(转换)
  • 可以把一个Dstream转换生成一个新的Dstream。它也是延迟加载,不会立即触发任务的运行
  • 类似于rdd的中transformation
  • 2、outputOperation(输出)
  • 它会触发任务的真正运行
  • 类似于rdd的中action

5、Dstream操作实战

  • 引入依赖  
•      <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.11</artifactId>
            <version>2.1.3</version>
        </dependency>

5.1 通过sparkStreaming接受socket数据实现单词统计

  • 1、代码开发 package demo.socket
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

//todo:利用sparkStreaming接受socket数据实现单词统计
object SparkStreamingSocket {
  def main(args: Array[String]): Unit = {
    //1、创建SparkConf对象
      val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocket").setMaster("local[2]")

    //2、创建SparkContext对象
      val sc = new SparkContext(sparkConf)
      sc.setLogLevel("warn")

    //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔
      val ssc = new StreamingContext(sc,Seconds(5))

    //4、接受socket数据
       val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)

    //5、切分每一行获取所有的单词
      val words: DStream[String] = socketTextStream.flatMap(_.split(" "))

    //6、每个单词计为1
      val wordAndOne: DStream[(String, Int)] = words.map((_,1))

    //7、相同单词出现的1累加
      val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)

    //8、打印
      result.print()

    //9、开启流式计算
      ssc.start()
      ssc.awaitTermination()
  }
}

5.2 通过sparkStreaming接受socket数据实现所有批次单词统计的结果累加

  • 1、代码开发 package demo.socket
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

//todo:利用sparkStreaming接受socket数据实现所有批次单词统计结果累加
object SparkStreamingSocketTotal {

  //currentValues:表示在当前批次中相同的单词出现的所有的1(hadoop---->List(1,1,1,1))
  //historyValues:表示每一个单词在之前所有批次中出现总次数

     //Option类型:可以表示可能存在或者不存在的值   存在Some,  不存在None
  def updateFunc(currentValues:Seq[Int],historyValues:Option[Int]):Option[Int] = {
       val newValue: Int = currentValues.sum  +  historyValues.getOrElse(0)
       Some(newValue)
  }

  def main(args: Array[String]): Unit = {
      //1、创建SparkConf
      val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketTotal").setMaster("local[2]")

      //2、创建SparkContext
      val sc = new SparkContext(sparkConf)
      sc.setLogLevel("warn")

     //3、创建StreamingContext
      val ssc = new StreamingContext(sc,Seconds(5))

       //设置checkpoint目录,主要的作用:用于保存之前批次每一个单词出现的总次数
        ssc.checkpoint("./socket")

     //4、接受socket数据
      val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)

    //5、切分每一行获取所有的单词
      val words: DStream[String] = socketTextStream.flatMap(_.split(" "))

    //6、每个单词计为1
      val wordAndOne: DStream[(String, Int)] = words.map((_,1))

    //7、相同单词出现的1累加
      val result: DStream[(String, Int)] = wordAndOne.updateStateByKey(updateFunc)

    //8、打印
      result.print()

    //9、开启流式计算
      ssc.start()
      ssc.awaitTermination()
  }
}

 

5.3 通过sparkStreaming接受socket数据,利用reduceByKeyAndWindow来实现单词统计

  • 1、代码开发 package demo.socket
• import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}

//todo:利用reduceByKeyAndWindow来实现单词统计
object SparkStreamingSocketWindow {
  def main(args: Array[String]): Unit = {
    //1、创建SparkConf对象
    val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketWindow").setMaster("local[2]")

    //2、创建SparkContext对象
    val sc = new SparkContext(sparkConf)
    sc.setLogLevel("warn")

    //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔
    val ssc = new StreamingContext(sc,Seconds(5))

    //4、接受socket数据
    val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)

    //5、切分每一行获取所有的单词
    val words: DStream[String] = socketTextStream.flatMap(_.split(" "))

    //6、每个单词计为1
    val wordAndOne: DStream[(String, Int)] = words.map((_,1))

    //7、相同单词出现的1累加
       //需要3个参数
//    reduceFunc: (V, V) => V,   它就是一个函数
//    windowDuration: Duration,  窗口的长度
//    slideDuration: Duration    窗口的滑动时间间隔,表示每隔多久计算一次
    val result: DStream[(String, Int)] = wordAndOne.reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(5),Seconds(10))

    //8、打印
    result.print()

    //9、开启流式计算
    ssc.start()
    ssc.awaitTermination()
  }
}

5.4 通过sparkStreaming使用开窗函数来统计一定内的热门词汇

  • 1.代码开发 package demo.socket
• import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

//todo:利用开窗函数来实现一定时间内的热门词汇
object SparkStreamingSocketWindowHotWords {
  def main(args: Array[String]): Unit = {
      //1、创建SparkConf
      val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketWindowHotWords").setMaster("local[2]")

      //2、创建SparkContext
      val sc = new SparkContext(sparkConf)
      sc.setLogLevel("warn")

     //3、创建StreamingContext
      val ssc = new StreamingContext(sc,Seconds(5))

    //4、接受socket数据
    val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)

    //5、切分每一行获取所有的单词
    val words: DStream[String] = socketTextStream.flatMap(_.split(" "))

    //6、每个单词计为1
    val wordAndOne: DStream[(String, Int)] = words.map((_,1))

    //7、相同单词出现的1累加
    val result: DStream[(String, Int)] = wordAndOne.reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(10),Seconds(10))

    //按照单词出现的次数降序排列
    val sortDstream: DStream[(String, Int)] = result.transform(rdd => {
      //按照单词次数降序
      val sortRDD: RDD[(String, Int)] = rdd.sortBy(_._2, false)
      //取出次数最多的前3位
      val top3: Array[(String, Int)] = sortRDD.take(3)
      println("---------------top3--------------start")
      top3.foreach(println)
      println("---------------top3--------------end")
      sortRDD
    })

    //8、打印
    sortDstream.print()

    //9、开启流式计算
    ssc.start()
    ssc.awaitTermination()
  }
}

6、sparkStreaming整合flume

6.1 Poll拉模式整合

  • 1、需要将spark-streaming-flume-sink_2.11-2.1.3.jar放入到flume的lib目录下,同时还需要把flume自带的scala依赖2.10改为2.11
  • 2、修改flume配置文件
• vim flume-poll-spark.conf a1.sources = r1
a1.sinks = k1
a1.channels = c1

#source
a1.sources.r1.channels = c1
a1.sources.r1.type = spooldir
a1.sources.r1.spoolDir = /root/data
a1.sources.r1.fileHeader = true


#channel
a1.channels.c1.type =memory
a1.channels.c1.capacity = 20000
a1.channels.c1.transactionCapacity=5000


#sinks
a1.sinks.k1.channel = c1
a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink
a1.sinks.k1.hostname=node1
a1.sinks.k1.port = 8888
a1.sinks.k1.batchSize= 2000
  • 3、引入依赖 <dependency>
•             <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming-flume_2.11</artifactId>
            <version>2.1.3</version>
        </dependency>
  • 4、代码开发 package demo.flume
• import java.net.InetSocketAddress

import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent}

//todo:利用sparkStreaming整合flume-------Poll拉模式整合
object SparkStreamingFlumePoll {
  def main(args: Array[String]): Unit = {
    //1、创建SparkConf对象
    val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingFlumePoll").setMaster("local[2]")

    //2、创建SparkContext对象
    val sc = new SparkContext(sparkConf)
    sc.setLogLevel("warn")

    //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔
    val ssc = new StreamingContext(sc,Seconds(5))

    //4、通过拉模式接受flume的数据
   val pollingStream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createPollingStream(ssc,"node1",8888)

     //接受多台flume收集到的数据
//        val addresses=List(new InetSocketAddress("node1",8888),new InetSocketAddress("node2",8888),new InetSocketAddress("node3",8888))
//       val stream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createPollingStream(ssc,addresses,StorageLevel.MEMORY_AND_DISK_SER_2)
//
    //5、获取flume中数据  flume中数据传输的最小单元是一个event:{"headers":xxxxx,"body":xxxxxx}
         //获取body中的数据
    val data: DStream[String] = pollingStream.map(x=>new String(x.event.getBody.array()))

    //6、切分每一行获取所有的单词
    val words: DStream[String] = data.flatMap(_.split(" "))

    //7、每个单词计为1
    val wordAndOne: DStream[(String, Int)] = words.map((_,1))

    //8、相同单词出现的1累加
    val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)

    //9、打印
    result.print()

    //10、开启流式计算
    ssc.start()
    ssc.awaitTermination()

  }
}

6.2 Push推模式整合

  • 1、编写flume的配置文件
    • vim flume-push-spark.conf #push mode
    a1.sources = r1
    a1.sinks = k1
    a1.channels = c1
    #source
    a1.sources.r1.channels = c1
    a1.sources.r1.type = spooldir
    a1.sources.r1.spoolDir = /root/data
    a1.sources.r1.fileHeader = true
    #channel
    a1.channels.c1.type =memory
    a1.channels.c1.capacity = 20000
    a1.channels.c1.transactionCapacity=5000
    #sinks
    a1.sinks.k1.channel = c1
    a1.sinks.k1.type = avro
    #是sparkStreaming应用程序所在的ip地址和端口
    a1.sinks.k1.hostname=192.168.25.48
    a1.sinks.k1.port = 8888
    a1.sinks.k1.batchSize= 2000
    • 2、代码开发 package demo.flume
    • import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
    import org.apache.spark.{SparkConf, SparkContext}
    import org.apache.spark.streaming.{Seconds, StreamingContext}
    import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent}
    
    //todo:sparkStreaming整合flume-------Push推模式
    object SparkStreamingFlumePush {
      def main(args: Array[String]): Unit = {
        //1、创建SparkConf对象
        val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingFlumePush").setMaster("local[2]")
    
        //2、创建SparkContext对象
        val sc = new SparkContext(sparkConf)
        sc.setLogLevel("warn")
    
        //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔
        val ssc = new StreamingContext(sc,Seconds(5))
    
        //4、接受flume中的数据
        val stream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createStream(ssc,"192.168.25.48",8888)
    
        //5、获取flume中数据  flume中数据传输的最小单元是一个event:{"headers":xxxxx,"body":xxxxxx}
        //获取body中的数据
        val data: DStream[String] = stream.map(x=>new String(x.event.getBody.array()))
    
        //6、切分每一行获取所有的单词
        val words: DStream[String] = data.flatMap(_.split(" "))
    
        //7、每个单词计为1
        val wordAndOne: DStream[(String, Int)] = words.map((_,1))
    
        //8、相同单词出现的1累加
        val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
    
        //9、打印
        result.print()
    
        //10、开启流式计算
        ssc.start()
        ssc.awaitTermination()
    
      }
    
    }

    7、SparkStreaming整合kafka

    • 引入依赖 <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-kafka-0-8_2.11</artifactId> <version>2.1.3</version> </dependency>

    7.1 KafkaUtils.createStream

    • 1、代码开发 package demo.kafka
    • import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
    import org.apache.spark.{SparkConf, SparkContext}
    import org.apache.spark.streaming.{Seconds, StreamingContext}
    import org.apache.spark.streaming.kafka.KafkaUtils
    
    import scala.collection.immutable
    
    //todo:sparkStreaming整合kafka -------基于receiver接收器,使用了kafka高层次消费者api(消息的偏移量由zk去维护)
    object SparkStreamingKafkaReceiver {
      def main(args: Array[String]): Unit = {
        //1、创建SparkConf对象
        val sparkConf: SparkConf = new SparkConf()
                                  .setAppName("SparkStreamingKafkaReceiver")
                                  .setMaster("local[4]")
          //开启WAL日志,作用:保证数据源端的安全性,后期某些rdd的分区数据丢失了,是可以通过 血统+原始数据进行恢复
                                    .set("spark.streaming.receiver.writeAheadLog.enable","true")
    
        //2、创建SparkContext对象
        val sc = new SparkContext(sparkConf)
        sc.setLogLevel("warn")
    
        //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔
        val ssc = new StreamingContext(sc,Seconds(5))
    
           //设置checkpoint目录,用于保存接受到的数据   实际工作中是指向HDFS目录
            ssc.checkpoint("./spark-receiver")
    
        //4、接受kafka中topic的数据
            //指定zk服务地址
            val zkQuorum="node1:2181,node2:2181,node3:2181"
           //指定消费者组id
           val groupId="spark-receiver"
           //指定topic相关信息  key:topic名称  value:表示一个receiver接收器使用多少个线程消费topic数据
            val topics=Map("itcast" ->1)
           //(String, String): 第一个String就是消息的key,第二个String就是消息的value
          //val kafkaDstream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc,zkQuorum,groupId,topics)
    
        //构建了3个receiver接收器去接受数据,加快数据的接受速度
        val kafkaDstreamList: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => {
          val kafkaDstream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics)
          kafkaDstream
        })
         //ssc.union方法 把多个receiver接受器产生的Dstream汇总成一个Dstream
          val totalKafkaDstream: DStream[(String, String)] = ssc.union(kafkaDstreamList)
    
    
        // kafkaDstream.print()
    
        //5、获取topic中的真实数据
          val data: DStream[String] = totalKafkaDstream.map(_._2)
    
        //6、切分每一行获取所有的单词
        val words: DStream[String] = data.flatMap(_.split(" "))
    
        //7、每个单词计为1
        val wordAndOne: DStream[(String, Int)] = words.map((_,1))
    
        //8、相同单词出现的1累加
        val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
    
        //9、打印
        result.print()
    
        //10、开启流式计算
        ssc.start()
        ssc.awaitTermination()
      }
    }
    • 2、总结 第一种方式使用了receiver接受器去接受数据,后期可以使用多个receiver接收器来加快接受的速度,使用了kafka高层次的消费者api,就是消息的偏移量由zk去维护。 默认会出现数据的丢失,可以启动WAL日志将接受到的数据同步写入到分布式文件系统HDFS上,保证数据源端它的安全性,后面就可以使用血统+原始数据,来重新计算恢复得到丢失的数据。 开启WAL日志: set("spark.streaming.receiver.writeAheadLog.enable","true") 需要设置数据保存目录: ssc.checkpoint("目录") 这种方式可以解决数据不丢失的问题,但是它解决不了数据被处理且只被处理一次。在这里由于更新偏移量到zk没有成功,导致数据正常消费成功了,没有把这个消费的标识记录下来,最后导致数据的重复消费。

    7.2 KafkaUtils.createDirectStream

    • 1、代码开发 package demo.kafka
    • import kafka.serializer.StringDecoder
    import org.apache.spark.streaming.dstream.{DStream, InputDStream}
    import org.apache.spark.{SparkConf, SparkContext}
    import org.apache.spark.streaming.{Seconds, StreamingContext}
    import org.apache.spark.streaming.kafka.KafkaUtils
    
    //todo:sparkStreaming整合kafka -------使用消费者低级api(消息的偏移量不在由zk去维护,由客户端程序自己去维护)
    object SparkStreamingKafkaDirect {
      def main(args: Array[String]): Unit = {
        //1、创建SparkConf对象
        val sparkConf: SparkConf = new SparkConf()
          .setAppName("SparkStreamingKafkaDirect")
          .setMaster("local[4]")
    
        //2、创建SparkContext对象
        val sc = new SparkContext(sparkConf)
        sc.setLogLevel("warn")
    
        //3、创建StreamingContext,需要2个 参数,第一个是SparkContext对象,第二个是批处理时间间隔
        val ssc = new StreamingContext(sc,Seconds(5))
    
          //设置checkpoint目录 用于保存消息消费的偏移量
          ssc.checkpoint("./spark-direct")
    
        //4、接受kafka中的数据
            val kafkaParams=Map("bootstrap.servers" ->"node1:9092,node2:9092,node3:9092","group.id" ->"spark-direct")
            val topics=Set("itcast")
    
          //通过下面这种方式获取得到的Dstream它内部的rdd分区数跟kafka中topic的分区数相等。
        val kafkaDstream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics)
    
        //5、获取topic中的真实数据
        val data: DStream[String] = kafkaDstream.map(_._2)
    
        //6、切分每一行获取所有的单词
        val words: DStream[String] = data.flatMap(_.split(" "))
    
        //7、每个单词计为1
        val wordAndOne: DStream[(String, Int)] = words.map((_,1))
    
        //8、相同单词出现的1累加
        val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
    
        //9、打印
        result.print()
    
        //10、开启流式计算
        ssc.start()
        ssc.awaitTermination()
      }
    }

    sparkle spark区别 sparking和sparkling_Spark

    7.3 打成jar包 集群运行


    spark-submit --master spark://node1:7077 --class cn.itcast.kafka.SparkStreamingKafkaDirect --executor-memory 1g --total-executor-cores 2 spark_class12-1.0-SNAPSHOT.jar 对于实时处理来说什么情况下是比较理想的状态? 在当前批次时间内就把上一个批次的数据处理完成。 每隔10s去处理上一个10s的数据 第一个10s的数据 -------------------------------------> 1分钟 第二个10s的数据 -------------------------------------> 1分钟 第三个10s的数据 -------------------------------------> 1分钟 ..... 后面来的批次数据,一直会等待,会出现数据积压。 在企业中,我们正常通过本地开发代码程序,可以通过指定master为local,先本地测试,测试后数据没问题,可以把程序打成jar提交到集群中运行。 访问:master主机名:8080 可以先来一些资源参数进行测试: --executor-memory 5g --total-executor-cores 10 --executor-memory 5g --total-executor-cores 20 --executor-memory 10g --total-executor-cores 20 --executor-memory 10g --total-e