大数据-100 Spark DStream 转换操作全面总结:map、reduceByKey 到 transform 的实战案例

点一下关注吧!!!非常感谢!!持续更新!!!

🚀 AI篇持续更新中!(长期更新)

AI炼丹日志-31- 千呼万唤始出来 GPT-5 发布!"快的模型 + 深度思考模型 + 实时路由",持续打造实用AI工具指南!📐🤖

💻 Java篇正式开启!(300篇)

目前2025年09月15日更新到: Java-124 深入浅出 MySQL Seata框架详解:分布式事务的四种模式与核心架构 MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

章节内容

上节我们完成了如下的内容:

  • Spark Streaming 基础数据源
  • 文件流、Socket流、RDD队列流
  • 引入依赖、Java编写多种流进行测试

DStream 转换

DStream上的操作与RDD类似,分为Transformations(转换)和 Output Operations(输出)两种,此外转换操作中还有一些比较特殊的方法,如:

  • updateStateByKey
  • transform
  • window相关操作

map(func)

对 DStream 中的每个元素应用 func 函数,并返回一个新的 DStream。 例如,将每个记录转换为其长度。 示例:val lengths = lines.map(line => line.length)

flatMap(func)

对 DStream 中的每个元素应用 func 函数,并将结果展平(即将集合的集合展开)。 例如,将每一行文本拆分为单词。 示例:val words = lines.flatMap(line => line.split(" "))

filter(func)

对 DStream 中的每个元素应用 func 函数,并保留返回值为 true 的元素。 例如,过滤掉长度小于 5 的单词。 示例:val filteredWords = words.filter(word => word.length > 5)

reduceByKey(func)

对键值对 DStream 进行聚合操作,对具有相同键的元素应用 func 函数。 例如,计算每个单词的总数。 示例:val wordCounts = words.map(word => (word, 1)).reduceByKey(_ + _)

groupByKey()

对键值对 DStream 中的每个键进行分组,并将具有相同键的值聚合到一个列表中。 示例:val grouped = pairs.groupByKey()

count()

统计 DStream 中每个 RDD 的元素个数。 示例:val count = words.count()

countByValue()

统计 DStream 中每个 RDD 中每个值的出现次数。 示例:val valueCounts = words.countByValue()

union(otherDStream)

将两个 DStream 合并为一个新的 DStream,包含两个 DStream 中的所有元素。 示例:val mergedStream = stream1.union(stream2)

join(otherDStream)

对两个键值对 DStream 进行连接操作,类似 SQL 中的 JOIN 操作。 示例:val joinedStream = stream1.join(stream2)

备注:

  • 在DStream与RDD上的转换操作非常类似(无状态操作)
  • DStream有自己特殊的操作(窗口操作、追踪状态变化操作)
  • 在DStream上的转换操作比RDD上的转换操作少

DStream 的转换操作可以分为 无状态(stateless)和 有状态(stateful)两种:

  • 无状态转换操作,每个批次的处理不依赖与之前批次的数据,常见的RDD转化操作,例如:map、Filter、reduceByKey等
  • 有状态转换操作,需要使用之前批次的数据或者是中间结果来计算当前批次的数据,有状态转换操作包括:基于滑动窗口的转换操作或追踪状态变化的转化操作

无状态转换

无状态转换是Spark Streaming中最基本的操作类型,它针对每个批次的RDD独立执行转换操作,不会保留任何历史状态信息。这种转换方式的特点是对每个时间窗口的数据处理都是独立的,不会跨批次维护状态。

基本无状态转换操作

以下是一些常见的无状态转换操作及其应用场景:

  1. map:对DStream中的每个元素应用给定的函数

    • 示例:将字符串转换为大写
    scala 复制代码
    val upper = lines.map(_.toUpperCase())
  2. flatMap:类似于map,但每个输入元素可以映射到0或多个输出元素

    • 示例:将每行文本拆分为单词
    scala 复制代码
    val words = lines.flatMap(_.split(" "))
  3. repartition:改变DStream的分区数量

    • 示例:将分区数调整为10个
    scala 复制代码
    val repartitioned = words.repartition(10)
  4. reduceByKey:对具有相同键的值进行归约

    • 示例:计算单词出现频率
    scala 复制代码
    val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)
  5. groupByKey:将具有相同键的值分组

    • 示例:按用户ID分组
    scala 复制代码
    val userEvents = events.groupByKey()

高级转换操作:transform

transform操作是Spark Streaming中最强大的无状态转换之一,它允许开发者直接操作DStream内部的RDD。其特点包括:

  1. 工作原理:

    • 对源DStream的每个RDD应用一个RDD-to-RDD函数
    • 在数据流的每个批次中都会被调用
    • 生成一个全新的DStream
  2. 典型应用场景:

    • 实现自定义的RDD操作
    • 组合多个RDD操作
    • 访问RDD特定的API(如join、cogroup等)
  3. 示例代码:

    scala 复制代码
    val filtered = words.transform { rdd =>
      // 可以在这里使用任何RDD操作
      rdd.filter(_.length > 3)
        .map(_.toUpperCase())
    }
  4. 实际应用案例:

    • 实时数据清洗:过滤掉不符合要求的数据
    • 特征工程:对原始数据进行复杂的特征提取
    • 数据关联:将流数据与静态数据集进行join操作

transform操作的优势在于它提供了最大的灵活性,开发者可以像操作普通RDD一样处理流数据,同时保持Spark Streaming的批处理特性。这使得Spark Streaming可以支持更复杂的业务逻辑,而不仅限于预定义的转换操作。

案例1 黑名单过滤

shell 复制代码
假设:arr1为黑名单数据(自定义),true表示数据生效,需要被过滤掉;false表示数据
未生效
val arr1 = Array(("spark", true), ("scala", false))
假设:流式数据格式为"time word",需要根据黑名单中的数据对流式数据执行过滤操
作。如"2 spark"要被过滤掉
1 hadoop
2 spark
3 scala
4 java
5 hive
结果:"2 spark" 被过滤

方案1 外连接实现

scala 复制代码
package icu.wzk

import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.ConstantInputDStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

object BlackListFilter1 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("BlackListFilter1")
      .setMaster("local[*]")
    val ssc = new StreamingContext(conf, Seconds(10))

    // 黑名单
    val blackList = Array(("spark", true), ("scala", true))
    val blackListRDD = ssc.sparkContext.makeRDD(blackList)

    // 测试数据
    val strArray: Array[String] = "spark java scala hadoop kafka hive hbase zookeeper"
      .split("\\s+")
      .zipWithIndex
      .map {
        case (word, index) => s"$index $word"
      }
    val rdd = ssc.sparkContext.makeRDD(strArray)
    val clickStream = new ConstantInputDStream(ssc, rdd)

    // 流式数据的处理
    val clickStreamFormatted = clickStream
      .map(value => (value.split(" ")(1), value))
    clickStreamFormatted.transform(clickRDD => {
      val joinedBlockListRDD: RDD[(String, (String, Option[Boolean]))] = clickRDD.leftOuterJoin(blackListRDD)
      joinedBlockListRDD.filter {
        case (word, (streamingLine, flag)) => {
          if (flag.getOrElse(false)) {
            false
          } else {
            true
          }
        }
      }.map {
        case (word, (streamingLine, flag)) => streamingLine
      }
    }).print()

    // 启动
    ssc.start()
    ssc.awaitTermination()
  }
}

方案1 运行结果

shell 复制代码
-------------------------------------------
Time: 1721618670000 ms
-------------------------------------------
5 hive
6 hbase
1 java
7 zookeeper
3 hadoop
4 kafka

... 下一批

对应的结果如下图所示:

方案2 SQL实现

scala 复制代码
package icu.wzk

import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.streaming.dstream.ConstantInputDStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

object BlackListFilter2 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("BlackListFilter2")
      .setMaster("local[*]")
    val ssc = new StreamingContext(conf, Seconds(10))
    ssc.sparkContext.setLogLevel("WARN")

    // 黑名单
    val blackList = Array(("spark", true), ("scala", true))
    val blackListRDD = ssc.sparkContext.makeRDD(blackList)

    // 生成测试 DStream
    val strArray: Array[String] = "spark java scala hadoop kafka hive hbase zookeeper"
      .split("\\s+")
      .zipWithIndex
      .map {
        case (word, index) => s"$index $word"
      }
    val rdd = ssc.sparkContext.makeRDD(strArray)
    val clickStream = new ConstantInputDStream(ssc, rdd)

    // 流式数据的处理
    val clickStreamFormatted = clickStream
      .map(value => (value.split(" ")(1), value))
    clickStreamFormatted.transform {
      clickRDD =>
        val spark = SparkSession
          .builder()
          .config(rdd.sparkContext.getConf)
          .getOrCreate()

        import spark.implicits._
        val clickDF: DataFrame = clickRDD.toDF("word", "line")
        val blackDF: DataFrame = blackListRDD.toDF("word", "flag")
        clickDF.join(blackDF, Seq("word"), "left")
          .filter("flag is null or flag == false")
          .select("line")
          .rdd
    }.print()

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

方案2 SQL运行结果

shell 复制代码
-------------------------------------------
Time: 1721619900000 ms
-------------------------------------------
[6 hbase]
[4 kafka]
[7 zookeeper]
[1 java]
[3 hadoop]
[5 hive]

运行结果截图如下图所示:

方案3 直接过滤

scala 复制代码
package icu.wzk

import org.apache.spark.SparkConf
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.streaming.dstream.ConstantInputDStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

object BlackListFilter3 {

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("BlackListFilter3")
      .setMaster("local[*]")
    val ssc = new StreamingContext(conf, Seconds(10))
    ssc.sparkContext.setLogLevel("WARN")

    // 黑名单
    val blackList = Array(("spark", true), ("scala", true))
    val blackListBC: Broadcast[Array[String]] = ssc
      .sparkContext
      .broadcast(blackList.filter(_._2).map(_._1))

    // 生成测试DStream
    val strArray: Array[String] = "spark java scala hadoop kafka hive hbase zookeeper"
      .split("\\s+")
      .zipWithIndex
      .map {
        case (word, index) => s"$index $word"
      }

    val rdd = ssc.sparkContext.makeRDD(strArray)
    val clickStream = new ConstantInputDStream(ssc, rdd)

    // 流式数据的处理
    clickStream.map(value => (value.split(" ")(1), value))
      .filter {
        case (word, _) => !blackListBC.value.contains(word)
      }
      .map(_._2)
      .print()

    // 启动
    ssc.start()
    ssc.awaitTermination()
    
  }
}

方案3 直接过滤运行结果

shell 复制代码
-------------------------------------------
Time: 1721627600000 ms
-------------------------------------------
1 java
3 hadoop
4 kafka
5 hive
6 hbase
7 zookeeper

... 下一批

运行结果如下图所示:

相关推荐
njsgcs1 小时前
sse mcp flask 开放mcp服务到内网
后端·python·flask·sse·mcp
数据智能老司机1 小时前
数据工程设计模式——冷热数据存储
大数据·设计模式·架构
间彧1 小时前
Java单例模式:饿汉式与懒汉式实现详解
后端
道可到1 小时前
百度面试真题 Java 面试通关笔记 04 |JMM 与 Happens-Before并发正确性的基石(面试可复述版)
java·后端·面试
Ray662 小时前
guide-rpc-framework笔记
后端
37手游后端团队2 小时前
Claude Code Review:让AI审核更懂你的代码
人工智能·后端·ai编程
长安不见2 小时前
解锁网络性能优化利器HTTP/2C
后端
LSTM972 小时前
使用Python对PDF进行拆分与合并
后端
用户298698530142 小时前
C#:将 HTML 转换为图像(Spire.Doc for .NET 为例)
后端·.net
程序员小假3 小时前
为什么这些 SQL 语句逻辑相同,性能却差异巨大?
java·后端