SparkStreaming

流(Streaming),在大数据时代为数据流处理,就像水流一样,是数据流;既然是数据流处理,就会想到数据的流入、数据的加工、数据的流出。

日常工作、生活中数据来源很多不同的地方。例如:工业时代的汽车制造、监控设备、工业设备会产生很多源数据;信息时代的电商网站、日志服务器、社交网络、金融交易系统、黑客攻击、垃圾邮件、交通监控等;通信时代的手机、平板、智能设备、物联网等会产生很多实时数据,数据流无处不在。

在大数据时代Spark Streaming能做什么?

平时用户都有网上购物的经历,用户在网站上进行的各种操作通过Spark Streaming流处理技术可以被监控,用户的购买爱好、关注度、交易等可以进行行为分析。在金融领域,通过Spark Streaming流处理技术可以对交易量很大的账号进行监控,防止罪犯洗钱、财产转移、防欺诈等。在网络安全性方面,黑客攻击时有发生,通过Spark Streaming流处理技术可以将某类可疑IP进行监控并结合机器学习训练模型匹配出当前请求是否属于黑客攻击。其他方面,如:垃圾邮件监控过滤、交通监控、网络监控、工业设备监控的背后都是Spark Streaming发挥强大流处理的地方。

大数据时代,数据价值一般怎么定义?

所有没经过流处理的数据都是无效数据或没有价值的数据;数据产生之后立即处理产生的价值是最大的,数据放置越久或越滞后其使用价值越低。以前绝大多数电商网站盈利走的是网络流量(即用户的访问量),如今,电商网站不仅仅需要关注流量、交易量,更重要的是要通过数据流技术让电商网站的各种数据流动起来,通过实时流动的数据及时分析、挖掘出各种有价值的数据;比如:对不同交易量的用户指定用户画像,从而提供不同服务质量;准对用户访问电商网站板块爱好及时推荐相关的信息。


Storm/JStrom:
  完全实时流式数据处理平台
  来一条数据就处理一条数据,对机器的性能要求比较高
  在高并发高数据量的情况下,延迟性比spark streaming的低
SparkStreaming:
  准实时/微观操作的流式数据处理平台
  Streaming是按照批次进行执行的,一个一个批次进行执行,一个批次的处理的数据就是批次对应时间段收集得到的数据,只有当上一个批次执行完成后,下一个批次才会开始执行
  相对于Storm来讲:
    数据的延迟性在大数据量的情况下比较高


SparkStreaming VS Hadoop MR:

Spark Streaming是一个准实时流处理框架,而Hadoop MR是一个离线、批处理框架;很显然,在数据的价值性角度,Spark Streaming完胜于Hadoop MR。

SparkStreaming VS Storm:

Spark Streaming是一个准实时流处理框架,处理响应时间一般以分钟为单位,也就是说处理实时数据的延迟时间是秒级别的;Storm是一个实时流处理框架,处理响应是毫秒级的。所以在流框架选型方面要看具体业务场景。需要澄清的是现在很多人认为Spark Streaming流处理运行不稳定、数据丢失、事务性支持不好等等,那是因为很多人不会驾驭Spark Streaming及Spark本身。在Spark Streaming流处理的延迟时间方面,Spark定制版本,会将Spark Streaming的延迟从秒级别推进到100毫秒之内甚至更少。

SparkStreaming优点:

1、提供了丰富的API,企业中能快速实现各种复杂的业务逻辑。

2、流入Spark Streaming的数据流通过和机器学习算法结合,完成机器模拟和图计算。

3、Spark Streaming基于Spark优秀的血统。

 

SparkStreaming能不能像Storm一样,一条一条处理数据?

Storm处理数据的方式是以条为单位来一条一条处理的,而Spark Streaming基于单位时间处理数据的,SparkStreaming能不能像Storm一样呢?答案是:可以的。

业界一般的做法是Spark Streaming和Kafka搭档即可达到这种效果


Kafka业界认同最主流的分布式消息框架,此框架即符合消息广播模式又符合消息队列模式。

Kafka内部使用的技术:

1、  Cache

2、  Interface

3、  Persistence(默认最大持久化一周)

4、  Zero-Copy技术让Kafka每秒吞吐量几百兆,而且数据只需要加载一次到内核提供其他应用程序使用

外部各种源数据推进(Push)Kafka,然后再通过Spark Streaming抓取(Pull)数据,抓取的数据量可以根据自己的实际情况确定每一秒中要处理多少数据。


Sparkstreaming的处理流程:
  -1. 读取数据形成DStream
    读取外部数据形成DStream,比如:KAFKA、Flume....
  -2. 数据处理
    DStream的API进行操作
  -3. 结果数据输出
    数据保存外部系统
 -a. Redis、MongoDB
 -b. RDBMs
 -c. HBASE、HDFS、Hive
 -d. Kafka
streaming应用结构:
[Flume -> ]Kafka -> SparkStreaming/Storm -> Kafka/HBase -> ...


最常用的应用场景:(业务比较简单)
  -1. 基本指标的统计
     活跃访客的统计
每个小时访客统计
最近三十分钟访客统计
各个省份最近三十分钟访客数量统计.....
  -2. 广告点击量统计
  -3. 黑名单统计
  -4. 对实时数据进行预测(预测模型在程序运行已经构造完成)


程序入口:
  StreamingContext:streaming的上下文对象,依赖SparkContext对象
  DStream: 核心抽象,可以当做RDD进行操作
  
==============SparkStreamingWordCount=================
  nc -lk 9999  //用于数据的输入
  sbin/start-dfs.sh  //启动hadoop文件系统
  bin/hive --service metastore &   //后台启动hive元数据服务
  bin/spark-shell --master local[2]  //启动Spark服务

import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._
val ssc = new StreamingContext(sc, Seconds(1))  //以一秒作为一个批次的数据输入
val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)
val result = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_,1)).reduceByKey(_+_)
result.print()
ssc.start()    // 启动开始进行处理的操作
ssc.awaitTermination()  // Wait for the computation to terminate

==============Scala实战Spark Streaming开发========

1、前期准备 Windows搭建好 Spark环境

2、配置Maven的Pom.xml文件


<dependency>
  <groupId>org.apache.sparkgroupId>
  <artifactId>spark-streaming_2.10artifactId>
  <version>${spark.version}version>
  <scope>compilescope>
dependency>

package com.yushu.bigdata.spark.app.streaming

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

object StreamingWordCount {
  def main(args: Array[String]): Unit = {
    // 1. 构建上下文
    /**
      * 当使用数据接收器的时候,因为在正常job之外需要一直运行一个task,
     * 所以需要占用一个线程====>在启动的时候,要求至少给定线程数量为2,一个线程一直接收数据,一个线程处理接收的数据
      */
    val conf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("streaming-wordcount")
    val sc = SparkContext.getOrCreate(conf)
    /**
      * batchDuration: 给定的是批次产生间隔时间,当一个批次产生的时候,
     * 这个批次会放入到一个待运行的队列中(先进先出),
     * 后台有专门的调度线程负责从待运行队列中获取批次进行执行,
     * 但是运行只有在上一个批次执行完成的情况下才能够运行当前批次;
     * 如果上一个批次运行的实际比较长,那么当前批次等待的时间也就比较长
      * ======> 一般情况要,要求批次产生的间隔大小要比批次的运行时间要大
      **/
    val ssc = new StreamingContext(sc, Seconds(10))
    val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)
    val result = dstream.flatMap(line => {
      line.split(" ")
    }).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)

    result.print()

    /**
      * TODO
      * 调用dstream的saveAsTextFiles结果保存到HDFS上的时候,会出现一个问题:每个批次一个文件夹
      */

    result.saveAsTextFiles("result/wc/r")

    // 将rdd进行转换操作,然后返回一个新的RDD
    dstream
      .transform(rdd => {
        // TODO: 一个批次只有一个RDD,不要考虑多个RDD的合并之类的问题
        rdd.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
      })
      .foreachRDD(rdd => {
        // TODO: 一个批次只有一个RDD,不要考虑多个RDD的合并之类的问题
        // 将DStream的数据输出问题,转换为RDD的数据输出
        rdd.repartition(1).foreachPartition(iter => iter.foreach(println))
      })

    // 启动开始进行处理的操作
    ssc.start() // Start the computation
    ssc.awaitTermination() // Wait for the computation to terminate
  }
}

温馨提示:

除了print()方法将处理后的数据输出之外,还有其他的方法也非常重要,在开发中需要重点掌握,比如SaveAsTextFile,SaveAsHadoopFile等,

最为重要的是foreachRDD方法,这个方法可以将数据写入Redis,DB,DashBoard等,甚至可以随意的定义数据放在哪里,功能非常强大。


==================SparkStreaming的程序运行原理=======================
  -1. 数据接收器(Receiver)
    Receiver接收数据的输入,并将输入的数据形成一个block块存储到内存/磁盘中(会进行备份)
 默认情况下200ms一个块,RDD执行的过程中,一个block块对应一个task任务
 spark.streaming.blockInterval:200ms ---> 控制数据接收器中块形成的间隔时间
 我们一般情况下,设置为1-2s,一般为: batchDuration/(executor num * 1~4)
Receiver接收到的块会实时的汇报给driver中的StreamingContext对象
  -2. batch/批次的产生
    每隔batchDuration指定的时间之后,产生一个可执行批次,这个批次就包含了在这个批次时间间隔内接受到的所有的block块数据,一般要求batchDuration是blockInterval的整数倍 ====> 这些所有的block块形成了rdd的数据输入,一个block块就是RDD的一个分区/一个task任务
  -3. 批次对应Job的执行(RDD的执行)
    一个job就是一个RDD的Action类型的API的触发,一个DStream的执行其实就是RDD的执行,一个批次可以包含多个rdd的job(只包含一个rdd)


==================DStream============================
  底层是由一系列的RDD和时间组成的集合,每个RDD包含了对应批次的数据
  DStream的API实际上是调用了RDD中的对应API
DStreams internally is characterized by a few basic properties:
 *  - A list of other DStreams that the DStream depends on
    DStream具有类似RDD的依赖机制
 *  - A time interval at which the DStream generates an RDD
    DStream会间隔性的产生RDD,间隔大小其实即使我们在构建StreamingContext的时候给定的batchDuration(默认情况),在DStream依赖中,子DStream的time interval和父的time interval是由关系的,一般就是相同
 *  - A function that is used to generate an RDD after each time interval
    有一个函数供产生RDD,当时间间隔到来的时候

RDD的销毁:
  在下下一个批次的执行完成的时候,当前批次产生的RDD会被销毁(RDD已经被执行过,而且streamingcontext中没有对该rdd有依赖的其它RDD)
  可以通过StreamingContext的api更改RDD的生命周期
     ssc.remember(Seconds(60)) ---> 给定的参数要求比原来的batchDuration大
 
===========================================================
Streaming的数据读取(数据源)
  -1. Basic Sources
    基于StreamingContext的API读取数据形成的DStream,都叫做Basic Sources;一般的应用场景是:利用数据接收器(Receiver)接收数据的业务,还有一些测试
 socketTextStream:读取tcp端口的数据,以文本形式读取
 socketStream: 读取tcp端口的数据,需要给定从数据流转换为数据的方式
 receiverStream: 根据你给定的一个数据接收器对象,构建一个DStream
  -2. Advanced Sources
    基于外部的API读取数据(非Streaming模块的API,非StreamingContext的API), 比如KAFKA、Flume.....
一般的都可以通过两种方式来获取数据,分布是:Use Receiver、Direct Approach


Streaming读取数据的方式
  -1. Use Receiver(使用数据接收器)
     在正常的job之外,启动一致运行的task专门用于数据接收功能,将接收到的数据保存到磁盘或者内存中,这种情况下,DStram形成的RDD中实际上存储的是block块id
  -2. Direct Approach(直接方式)
     StreamingContext在形成RDD的时候,直接保存数据的存储位置信息以及相关参数,然后在rdd的job执行的时候,才会通过数据源(软件)所提供的API根据位置信息及参数获取数据===>数据不会形成Block块保存Executor中

==================SparkStreaming和Kafka集成==========================
  http://spark.apache.org/docs/1.6.1/streaming-kafka-integration.html

org.apache.spark
spark-streaming-kafka_2.10
${spark.version}

  -1.  Receiver-based Approach(Use Receiver)
    基于Kafka的High level  Consumer API进行数据的消费操作
kafka的数据会保存到executor中,以block块的形式保存,最终RDD的分区数量/Task数量对应block块的数量
  -2. Direct Approach
    val directKafkaStream = KafkaUtils.createDirectStream[
     [key class], [value class], [key decoder class], [value decoder class] ](
     streamingContext, [map of Kafka parameters], [set of topics to consume])
    基于Kafka的Simple Consumer API进行数据读取,调用的时候必须给定相关的一些从哪儿开始读取数据的方式
最终的RDD中,一个Task对应一个分区的数据

=====================Receiver-based Approach方式=======================

import kafka.serializer.StringDecoder
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object KafkaReceiverStreamingWordCount {
  def main(args: Array[String]): Unit = {
    // 1. 构建上下文
    val conf = new SparkConf()
      .setMaster("local[10]")
      .setAppName("KafkaReceiverStreamingWordCount")
      .set("spark.streaming.blockInterval", "1s") //指定bolck块形成的间隔时间,对应RDD的一个分区
    val sc = SparkContext.getOrCreate(conf)
    val ssc = new StreamingContext(sc, Seconds(10))  //指定多长时间生成一个批次的数据

    // 读取DStream
    // kafka连接zk的信息
    val zkQuorum: String = "bigdata-01.yushu.com:2181/kafka"
    // 给定consumer的group id是啥
    val groupId: String = "streaming3"
    // 给定需要读取的topic名称以及该topic需要使用多少个线程来读取数据
    val topics: Map[String, Int] = Map("yushu1" -> 2)

    // 给定kafka的consumer的相关配置信息
    val kafkaParams = Map[String, String](
      "zookeeper.connect" -> zkQuorum,
      "group.id" -> groupId,
      "zookeeper.connection.timeout.ms" -> "10000",
      "auto.offset.reset" -> "smallest")


    // 方式一:
//    val dstream1 = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
    // 方式二:
//    val dstream2 = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)

    // 使用Union合并dstream,当receiver的情况下,可以提高数据的处理能力
    val dstream21 = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
    val dstream22 = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
    val dstream23 = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
    val dstream3 = dstream21.union(dstream22).union(dstream23)

    val dstream = dstream3
    // DStream的操作
    val result = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
    result.print()

    // 启动开始进行处理的操作
    ssc.start() // Start the computation
    ssc.awaitTermination() // Wait for the computation to terminate
  }
}

=========================Direct Approach==================

import kafka.common.TopicAndPartition
import kafka.message.MessageAndMetadata
import kafka.serializer.StringDecoder
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object KafkaDirectStreamingWordCount {
  def main(args: Array[String]): Unit = {
    // 1. 构建上下文
    val conf = new SparkConf()
      .setMaster("local[10]")
      .setAppName("KafkaDirectStreamingWordCount")
    val sc = SparkContext.getOrCreate(conf)
    val ssc = new StreamingContext(sc, Seconds(10))

    // 读取DStream
    // 给定通过Simple Consumer API连接kafka的时候需要的配置参数: metadata.broker.list,auto.offset.reset
    val kafkaParams = Map[String, String](
      "metadata.broker.list" -> "bigdata-01.yushu.com:9092,bigdata-01.yushu.com:9093,bigdata-01.yushu.com:9094,
bigdata-01.yushu.com:9095",
      "auto.offset.reset" -> "smallest")
    // 给定需要读取数据的topic名称
    val topics = Set("yushu1")
    // 给定读取数据的分区信息语句偏移量的值(需要明确给定读取那些topic的那些分区的数据<从哪个offset开始读取>)
    val fromOffsets: Map[TopicAndPartition, Long] = Map(
      TopicAndPartition("yushu1", 0) -> 0,
      TopicAndPartition("yushu1", 1) -> 100
    )
    // 给定数据转换函数(定义对于kafka的数据可以进行如何转换操作)
    val messageHandler: MessageAndMetadata[String, String] => String = message => {
      // TODO:在这里可以获取偏移量、topic名称、分区id、key
     /* message.topic
      message.partition
      message.offset
      message.key()*/
      message.message()
    }

    // 方式一
//    val dstream1 = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics).map(_._2)
    // 方式二
    val dstream2 = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder, String](ssc, kafkaParams, fromOffsets, messageHandler)


    val dstream = dstream2
    // DStream的操作
    val result = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
    result.print()

    // 启动开始进行处理的操作
    ssc.start() // Start the computation
    ssc.awaitTermination() // Wait for the computation to terminate
  }
}
==============================================================
SparkStreaming和Kafka集成的优化方式
  所有SparkCore能够做的优化措施,SparkStreaming均可以做
  -1. user receiver
    -a. 将参数spark.streaming.blockInterval进行调整
-b. 考虑使用多个数据接收器,然后通过union API将dstream进行合并
  要求:
    合并的dstream的ssc属性是同一个
要求合并的dstream的批次间隔时间是一样的
  -2. direct
    -a. 开启动态数据处理机制
  spark.streaming.backpressure.enabled:false, 设置为true,表示开启
  spark.streaming.receiver.maxRate:设置数据接收器的最大接收能力(一个批次能够最多接收多少数据)
  spark.streaming.kafka.maxRatePerPartition: 指定读取kafka数据的时候,一个批次每个分区最多读取多少条数据(每个task允许最多读取多少数据)


=====SparkStreaming和Kafka集成的时候consumer的offset偏移量管理方式========

  目标:当steaming应用程序宕机后,进行恢复,恢复的时候期望的已经的数据不再处理,没有处理的开始进行处理 ===> 能够在恢复应用的过程中同时恢复对于offset偏移量的值
  -1. use receiver
    只能基于kafka自带的offset管理机制进行偏移量的管理(间隔性的将offset提交到zk上)
  -2. direct
    -a. 可以自定义messageHandler函数,将处理数据的offset偏移量进行提交保存第三方系统来通过代码保证消息的可靠性
-b. 使用streaming中的HA机制
 DStream在创建RDD的过程中,会自动将RDD对应的元数据保存到HDFS上对应文件夹的checkpoint文件夹中

Streaming HA机制
  Spark Streaming中提供的一种元数据恢复功能,通过将元数据保存到HDFS上的文件夹,然后当streaming应用恢复的时候,从该文件夹加载数据,该机制通过代码实现


====================Streaming HA机制==============================
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object HAStreamingWordCount {
  def main(args: Array[String]): Unit = {
    // 1. 构建上下文
    val conf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("HAStreamingWordCount")
    val sc = SparkContext.getOrCreate(conf)
    // 一般情况下,该路径为hdfs上的文件夹,第一次是空的或者不存在的
    val path = "hdfs://bigdata-01.yushu.com:8020/yushu/spark/streaming/chk/01"

    // 构建StreamingContext对象以及DStream的操作, 所以dstream的操作必须放到该函数中
    def creatingStreamingContextFunc(): StreamingContext = {
      val ssc = new StreamingContext(sc, Seconds(10))

      val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)
      // TODO: 对于修复HA的streaming的程序的bug情况下,不能改变DStream的依赖关系
      // TODO: 所以,在HA的streaming应用中,一般使用mapPartitions该API的比较多
      val result = dstream.flatMap(line => {
        line.split(" ")
      }).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
      result.print()

      // 设置checkpoint的地址
      ssc.checkpoint(path)

      ssc
    }

    // 如果checkpointPath对应的文件夹中存储着streaming应用的元数据,进行恢复加载加载;如果没有,就使用给定的函数进行创建操作
    val ssc = StreamingContext.getActiveOrCreate(
      checkpointPath = path,
      creatingFunc = creatingStreamingContextFunc
    )

    // 启动开始进行处理的操作
    ssc.start() // Start the computation
    ssc.awaitTermination() // Wait for the computation to terminate
  }
}

===========================transform======================================

  直接操作DStream中当前批次的RDD,通过这种方式可以替代DStream的API调用;该api的返回结果是DStream
  注意:不管是什么类型的DStream,一个批次中只有一个RDD


DStream的数据输出
  -1. 调用DStream的相关API进行数据输出
  -2. 通过foreachRDD API将DStream的数据输出转换为RDD的数据输出

    foreachRDD API类似transform API,区别在于:foreachRDD没有数据返回值


========================updateStateByKey======================================

  updateStateByKey一般情况下需要和Streaming的HA一起使用
  应用场景:需要对数据进行累加
  updateStateByKeyAPI的使用必须给定checkpoint文件夹


================================案例====================================

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

object UpdateStateByKeyStreamingWordCount {
  def main(args: Array[String]): Unit = {
    // 1. 构建上下文
    val conf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("UpdateStateByKeyStreamingWordCount")
    val sc = SparkContext.getOrCreate(conf)
    val ssc = new StreamingContext(sc, Seconds(10))
    // 在使用updateStateByKey的时候必须给定
    ssc.checkpoint(s"hdfs://bigdata-01.yushu.com:8020/yushu/spark/streaming/chk/${System.currentTimeMillis()}")

    val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)//监听该主机 该端口的数据
    // 当前批次的结果
    val wordCountDStream = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
    // 对之前的状态值和当前批次的结果进行合并/聚合操作
    /**
      * def updateStateByKey[S: ClassTag](
      * updateFunc: (Seq[V], Option[S]) => Option[S]
      * ): DStream[(K, S)]
      * 功能:对当前批次的数据和之前的状态信息按照key进行分组后,对value的数据进行聚合操作
      * Seq[V]和Option[S]: 对应是同一个key
      * Seq[V]: 对应的是当前批次中某一个key分组后所有values的值
      * Option[S]:对应的是之前执行过程中某一个key对应的状态信息, 如果之前没有状态信息,值为None
      * updateFunc函数的返回值是某一个key经过当前批次执行后的需要保存的状态信息,也值执行结果
      */
    val result = wordCountDStream.updateStateByKey((values: Seq[Int], stats: Option[Long]) => {
      // 聚合当前状态的数据, 如果当前批次没有对应的key值,那么values这个序列为空
      val currentValue = values.sum

      // 获取上一个状态的值
      val preValue = stats.getOrElse(0L)

      // 更新状态值,并返回新的状态值
      Some(preValue + currentValue)

      
    })
    result.print()

    // 启动开始进行处理的操作
    ssc.start() // Start the computation
    ssc.awaitTermination() // Wait for the computation to terminate
  }
}

=================WindowStreamingWordCount===================

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

object WindowStreamingWordCount {
  def main(args: Array[String]): Unit = {
    // 1. 构建上下文
    val conf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("WindowStreamingWordCount")
    val sc = SparkContext.getOrCreate(conf)
    val ssc = new StreamingContext(sc, Seconds(1))
    ssc.checkpoint(s"hdfs://bigdata-01.yushu.com:8020/yushu/spark/streaming/chk/${System.currentTimeMillis()}")

    val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)
    val wordDStream = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1))
    val result = wordDStream.reduceByKeyAndWindow(
      (a: Int, b: Int) => a + b, // 指定按照key分组后,数据聚合的函数
      (c: Int, d: Int) => c - d, // c指的是上一个执行批次的结果,d是上一个执行批次和当前执行批次没有重叠的那一部分(上一个执行批次中)
      Seconds(5), // windowDuration: Duration ===> 指定形成的新DStream所包含的时间范围,也就是指定需要计算最近多久的数据;要求该值必须是父DStream的批次产生时间的整数倍
      Seconds(3) //  slideDuration: Duration ===> 指定新的DStream多久产生一个批次,也就是多久执行一次;要求该值必须是父DStream的批次产生时间的整数倍
    )
    result.print()

    // 启动开始进行处理的操作
    ssc.start() // Start the computation
    ssc.awaitTermination() // Wait for the computation to terminate
  }
}

======================= SparkStreaming应用场景=====================

  -1. 实时统计(一个批次批次的统计)
  -2. 实时的累加操作
    updateStateByKey
  -3. 最近一段时间的相关指标
    window类型的API

xxxByWindow
  应用场景:计算最近一段时间的数据
  xxxByWindow相关的API必须给定checkpoint文件夹路径



你可能感兴趣的:(大数据)