SparkStreaming---DStream

本文介绍了SparkStreaming中的DStream概念,包括从RDD队列创建DStream、自定义数据源的使用、无状态转换如transform和join,以及有状态转换如UpdateStateByKey和WindowOperations。讲解了如何在SparkStreaming中处理实时数据流并进行复杂操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.DStream是什么

参考博文SparkStreaming入门

2.DStream创建

2.1 RDD队列

可以通过使用 queueStream(queueOfRDDs)来创建 DStream,每一个推送到这个队列中的 RDD,都会作为一个 DStream 处理。

  def main(args: Array[String]): Unit = {
    //1.初始化 Spark 配置信息
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamWordCount")
    //2.初始化 SparkStreamingContext
    //处理区间为3s一次
    val ssc = new StreamingContext(sparkConf, Seconds(3))

    //创建RDD队列
    val rddQueue = new mutable.Queue[RDD[Int]]()

    //创建QueueInputDStream
    //oneAtTime=false表示同时处理队列中所有的RDD
    val inputStream: InputDStream[Int] = ssc.queueStream(rddQueue, oneAtATime = false)

    //对DStream处理
    val mapStream: DStream[(Int, Int)] = inputStream.map((_, 1))
    val resDStream: DStream[(Int, Int)] = mapStream.reduceByKey(_ + _)

    resDStream.print()

    //启动SparkStreamingContext
    ssc.start()

    //向RDD队列中放入RDD
    for(i <- 1 to 5) {
    rddQueue+=ssc.sparkContext.makeRDD(List(1,2,3,4))
    Thread.sleep(2000)
    }
    //等待接收数据
    ssc.awaitTermination()
  }

在这里插入图片描述

2.2 自定义数据源

用户自定义数据源需要继承 Receiver,并实现 onStart、onStop 方法来自定义数据源采集。

class MyReceiver(host:String,port:Int) extends Receiver[String](StorageLevel.MEMORY_ONLY)
{
  //最初启动的时候,调用该方法,读数据并将数据发送给 Spark
  override def onStart(): Unit = {
    new Thread("Receiver"){
      override def run(){
        receive()
      }
    }.start()
  }

  ///读数据并将数据发送给 Spark
  def receive(): Unit = {
    //创建Socket
    val socket: Socket = new Socket(host,port)
    //创建变量用于接收端口穿过来的数据
    var input:String=null
    //创建BufferedReader用于读取端口传来的数据
    val reader: BufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream, StandardCharsets.UTF_8))

    //读取数据
    input=reader.readLine()
    //当 receiver 没有关闭并且输入数据不为空
    while(!isStopped() && input != null){
      //循环发送数据给 Spark
      store(input)
      input=reader.readLine()
    }

    //跳出循环则关闭资源
    reader.close()
    socket.close()

    //重启任务
    restart("restart")
  }

  override def onStop(): Unit = {

  }
}

使用自定义数据源采集数据

  def main(args: Array[String]): Unit = {
    //1.初始化 Spark 配置信息
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamWordCount")
    //2.初始化 SparkStreamingContext
    //处理区间为3s一次
    val ssc = new StreamingContext(sparkConf, Seconds(3))

    //3.创建自定义 receiver 的 Streaming
    val lineStream = ssc.receiverStream(new MyReceiver("localhost", 9999))


    //4.处理接收的数据
    val words: DStream[String] = lineStream.flatMap(_.split(" "))
    val wordMap: DStream[(String, Int)] = words.map((_, 1))
    val res = wordMap.reduceByKey(_ + _)

    res.print()

    //启动SparkStreamingContext
    ssc.start()


    //等待接收数据
    ssc.awaitTermination()
  }

在这里插入图片描述

3.DStream转换

在SparkStreaming中,各个DStream之间是相互独立的,互不干扰的。

DStream 上的操作与 RDD 的类似,分为 Transformations(转换)和 Output Operations(输出)两种,此外转换操作中还有一些比较特殊的原语,如:updateStateByKey()、transform()以及各种 Window 相关的原语。

3.1 无状态转换

无状态转化操作就是把简单的 RDD 转化操作应用到每个批次上,也就是转化 DStream 中的每一个 RDD。即一次对一个批次的RDD执行转换操作。

3.1.1 Transformations

在Spark Streaming中,transform操作是一个强大的功能,允许你将一个DStream转换成一个新的DStream。 它类似于Spark Core中的flatMap和map操作。你可以使用它来执行各种转换操作,例如过滤、分组、连接等。原RDD保持不变。

  def main(args: Array[String]): Unit = {
    //1.初始化 Spark 配置信息
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamWordCount")
    //2.初始化 SparkStreamingContext
    //处理区间为3s一次
    val ssc = new StreamingContext(sparkConf, Seconds(3))

    //3.通过监控端口创建 DStream,读进来的数据为一行行
    val lines = ssc.socketTextStream("localhost", 9999)

    //4.使用transform来转换
    val resTransform = lines.transform(rdd => {
      val words = rdd.flatMap(_.split(" "))
      val wordMap = words.map((_, 1))
      val res = wordMap.reduceByKey(_ + _)
      res
    })

    //打印
    resTransform.print()

    //启动SparkStreamingContext
    ssc.start()


    //等待接收数据
    ssc.awaitTermination()
  }

在这里插入图片描述

3.1.2 join

两个DStream流之间的 join 需要两个流的批次大小一致,这样才能做到同时触发计算。计算过程就是对当前批次的两个流中各自的 RDD 进行 join,与两个 RDD 的 join 效果相同。

  def main(args: Array[String]): Unit = {
    //1.初始化 Spark 配置信息
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamWordCount")
    //2.初始化 SparkStreamingContext
    //处理区间为3s一次
    val ssc = new StreamingContext(sparkConf, Seconds(3))

    //3.通过监控端口创建 DStream,读进来的数据为一行行
    val line9999: ReceiverInputDStream[String] = ssc.socketTextStream("localhost", 9999)
    val line8888: ReceiverInputDStream[String] = ssc.socketTextStream("localhost", 8888)

    //4.将两个流转换为K,V类型
    val lines1: DStream[(String, Int)] = line8888.flatMap(_.split(" ")).map((_,1))
    val lines2: DStream[(String, Int)] = line9999.flatMap(_.split(" ")).map((_,11))

    //5.使用join来对两个DStream进行连接
    var resTransform: DStream[(String, (Int, Int))] =lines1.join(lines2)

    //打印
    resTransform.print()

    //启动SparkStreamingContext
    ssc.start()


    //等待接收数据
    ssc.awaitTermination()
  }

在这里插入图片描述

3.2 有状态转换操作

在Spark Streaming中,有状态转换操作是指那些需要依赖之前批次数据或中间结果来计算当前批次数据的操作。这些操作在处理数据时,会在跨时间区间内跟踪数据的状态变化。在有状态转换的过程中,需要借助检查点(checkpoint)来完成转换。

3.2.1 UpdateStateByKey

UpdateStateByKey用于记录历史记录,有时,我们需要在 DStream 中跨批次维护状态(例如流计算中累加 wordcount)。针对这种情况,updateStateByKey()为我们提供了对一个状态变量的访问,用于键值对形式的 DStream。给定一个由(键,事件)对构成的 DStream,并传递一个指定如何根据新的事件更新每个键对应状态的函数,它可以构建出一个新的 DStream,其内部数据为(键,状态) 对。
updateStateByKey() 的结果会是一个新的 DStream,其内部的 RDD 序列是由每个时间区间对应的(键,状态)对组成的。

  def main(args: Array[String]): Unit = {

    //定义状态更新方法:参数 seq 为当前批次单词数量,state 为以往批次单词数量
    val updateFuc = (seq:Seq[Int],state:Option[Int])=>{
      //当前批次的单词数量
      val updateSeq: Int = seq.foldLeft(0)((x, y) => x + y)
      //以往批次的单词数量
      val updateState:Int = state.getOrElse(0)
      Some(updateSeq+updateState)
    }

    //1.初始化 Spark 配置信息
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamWordCount")
    //2.初始化 SparkStreamingContext
    //处理区间为3s一次
    val ssc = new StreamingContext(sparkConf, Seconds(3))

    ssc.checkpoint("./ck")

    val line9999: ReceiverInputDStream[String] = ssc.socketTextStream("localhost", 9999)

    val wordMap: DStream[(String, Int)] = line9999.flatMap(_.split(" ")).map((_,1))

    val res = wordMap.updateStateByKey[Int](updateFuc)


    //打印
    res.print()

    //启动SparkStreamingContext
    ssc.start()


    //等待接收数据
    ssc.awaitTermination()
  }

3.2.2 WindowOperations

Window Operations 可以设置窗口的大小和滑动窗口的间隔来动态的获取当前 Steaming 的允许状态。所有基于窗口的操作都需要两个参数,分别为窗口时长以及滑动步长

注意:窗口时长和滑动步长这两者都必须为采集周期大小的整数倍。

实现需求:WordCount案例:3 秒一个批次,窗口 12 秒,滑步 6 秒。

  def main(args: Array[String]): Unit = {

    //1.初始化 Spark 配置信息
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamWordCount")
    //2.初始化 SparkStreamingContext
    //处理区间为3s一次
    val ssc = new StreamingContext(sparkConf, Seconds(3))

    ssc.checkpoint("./ck")

    val lines = ssc.socketTextStream("localhost", 9999)
    val dataMap: DStream[(String, Int)] = lines.flatMap(_.split(" ")).map((_, 1))

    //窗口 12 秒,滑步 6 秒
    val wordCountByWindows = dataMap.reduceByKeyAndWindow((a: Int, b: Int) => {
      a + b
    }, Seconds(12), Seconds(6))

    wordCountByWindows.print()

    //启动SparkStreamingContext
    ssc.start()
    //等待接收数据
    ssc.awaitTermination()
  }

reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]):
当在一个(K,V)对的DStream上调用此函数,会返回一个新(K,V)对的DStream,此处通过对滑动窗口中批次数据使用
reduce 函数来整合每个 key 的 value 值。
参数解答:

windowLength 是滑动窗口的大小。
slideInterval 是滑动窗口的滑动间隔。这意味着每6秒,窗口会向前滑动一次。

关于 Window 的操作还有如下方法:

(1)window(windowLength, slideInterval): 基于对源 DStream 窗化的批次进行计算返回一个新的 Dstream;
(2)countByWindow(windowLength, slideInterval): 返回一个滑动窗口计数流中的元素个数;
(3)reduceByWindow(func, windowLength, slideInterval): 通过使用自定义函数整合滑动区间流元素来创建>一个新的单元素流;
(4)reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]): 这个函数是上述函数的变化版本,每个窗口的 reduce 值都是通过用前一个窗的 reduce 值来递增计算。通过 reduce 进入到滑动窗口数据并”反向 reduce”离开窗口的旧数据来实现这个操作。一个例子是随着窗口滑动对 keys 的“加”“减”计数。当窗口较大而滑动步数较小时,可以使用该函数来避免重复计算。

4.DStream输出

输出操作指定了对流数据经转化操作得到的数据所要执行的操作(例如把结果推入外部数据库或输出到屏幕上)。与 RDD 中的惰性求值类似,如果一个 DStream 及其派生出的 DStream 都没有被执行输出操作,那么这些 DStream 就都不会被求值。 如果 StreamingContext 中没有设定输出操作,整个 context 就都不会启动。
在这里插入图片描述

### 如何使用 Scala 进行 Spark Streaming 开发 #### 1. 环境准备 为了在 Windows 上使用 Scala 和 Apache Spark 进行开发,首先需要完成必要的环境设置。这包括安装 Scala 并配置其运行环境[^4]。 ```bash # 下载并解压 Scala 安装包到指定目录 https://www.scala-lang.org/download/ # 配置环境变量 SCALA_HOME 和 PATH export SCALA_HOME=/path/to/scala export PATH=$PATH:$SCALA_HOME/bin ``` 接着,确保已正确安装 JDK,并将其路径加入 `JAVA_HOME` 中。这是因为在 JVM 上运行的应用程序(如 ScalaSpark)都需要 Java 支持。 --- #### 2. 创建 Spark Conf 和 Streaming Context 在编写 Spark Streaming 应用之前,需初始化 `SparkConf` 对象来定义应用程序名称及其执行模式(本地或集群)。随后创建 `StreamingContext` 来管理流式计算任务[^3]。 ```scala import org.apache.spark.SparkConf import org.apache.spark.streaming.{Seconds, StreamingContext} val appName = "MySparkStreamingApp" val master = "local[*]" // 或者 cluster URL 如果是在分布式环境中运行 // 初始化 SparkConf val conf = new SparkConf() .setAppName(appName) .setMaster(master) // 创建 StreamingContext,每秒触发一次批处理操作 val ssc = new StreamingContext(conf, Seconds(1)) ``` 上述代码片段展示了如何构建基本的 Spark 流程框架。其中 `Seconds(1)` 表示每隔一秒会启动一个新的批次作业。 --- #### 3. 数据源接入与 DStream 处理 DStreamSpark Streaming 的核心抽象之一,代表连续的数据流。可以通过多种方式获取输入数据流,比如文件系统、套接字连接或者 Kafka 消息队列等[^2]。 以下是基于 Socket 输入的一个简单例子: ```scala // 接收来自网络端口的数据流 (假设服务器监听于 localhost:9999) val lines = ssc.socketTextStream("localhost", 9999) // 将每一行拆分为单词并统计频率 val words = lines.flatMap(_.split(" ")) val wordCounts = words.map(word => (word, 1)).reduceByKey(_ + _) // 打印结果至控制台 wordCounts.print() ssc.start() // 启动 StreamingContext ssc.awaitTermination() // 等待终止信号 ``` 此部分实现了从远程主机读取文本消息的功能,并对这些字符串中的词语进行了计数分析。 --- #### 4. RDDs 及其转换逻辑 尽管 Spark Streaming 主要围绕着 DStreams 展开工作,但底层仍然依赖于 RDD(弹性分布式数据集),这意味着开发者也可以利用熟悉的 RDD API 设计复杂的业务流程。 例如,在上面的例子中我们调用了几个常见的方法: - **flatMap**: 把单个记录映射成多个子项; - **map**: 转换每个元素的形式; - **reduceByKey**: 按键聚合数值。 如果需求更加复杂,则可能涉及窗口函数(window functions),状态维护(stateful operations)等内容。 --- #### 总结 Scala 不仅是一种强大的编程语言,而且它还特别适合用来编写像 Spark 这样的大数据平台上的应用软件,因为两者共享相同的虚拟机架构——Java Virtual Machine(JVM)[^1]。通过本文介绍的方法论,读者应该已经掌握了怎样快速入门 Spark Streaming 的基础技能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值