Spark-Streaming初识

一、Spark Streaming是什么?

Spark Streaming是Spark的上一代流式计算引擎。Spark Streaming不再有更新,它是一个遗留项目。Spark中有一个更新且更易于使用的流式计算引擎:Structured Streaming。因此我们只要做到初步了解并简单使用Spark Streaming就可以了。

下面就让我们跟着官网来学习吧

Spark Streaming - Spark 3.5.3 Documentation

二、概览

Spark Streaming是核心Spark API的扩展,它支持实时数据流的可扩展、高吞吐量、容错流处理。

  • 数据来源:Kafka、Kinesis或TCP socket
  • 数据处理:如mapreducejoinwindow等高级函数,也可以使用Spark的机器学习和图形计算算法
  • 输出:推送到文件系统、数据库和实时仪表板

Spark Streaming接收实时输入数据流并将数据分成批次进行处理来生成结果流。【微批

Spark Streaming提供了一种称为离散流DStream的高级抽象,它表示连续的数据流。DStreams可以从Kafka和Kinesis等源的输入数据流创建,也可以通过对其他DStreams应用高级操作来创建。在内部,DStream表示为一系列RDD。

三、入门例子

下面我们用一个例子理解Spark Streaming的数据处理过程。

假设我们要计算从侦听TCP socket 的数据服务器接收到的文本数据中的字数

1、添加maven依赖

<dependency>

<groupId>org.apache.spark</groupId>

<artifactId>spark-streaming_2.12</artifactId>

<version>3.5.3</version>

<scope>provided</scope>

</dependency>

Spark Streaming核心API中不存在从Kafka和Kinesis等源获取数据,需要单独添加依赖

<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-streaming-kafka-0-10 -->

<dependency>

<groupId>org.apache.spark</groupId>

<artifactId>spark-streaming-kafka-0-10_2.12</artifactId>

<version>2.4.4</version>

</dependency>

2、官方例子

Scala 复制代码
object NetworkWordCount {
  def main(args: Array[String]): Unit = {
    if (args.length < 2) {
      System.err.println("Usage: NetworkWordCount <hostname> <port>")
      System.exit(1)
    }

    StreamingExamples.setStreamingLogLevels()

    // 创建一个具有2个线程和1秒批处理间隔的本地StreamingContext。
    val sparkConf = new SparkConf().setAppName("NetworkWordCount")
    val ssc = new StreamingContext(sparkConf, Seconds(1))

    // 创建一个 DStream 去连接 hostname:port
    //linesDStream表示将从数据服务器接收的数据流,此DStream中的每条记录都是一行文本
    val lines = ssc.socketTextStream(args(0), args(1).toInt, StorageLevel.MEMORY_AND_DISK_SER)
    // 按空格字符将行拆分为单词
    // flatMap是一个一对多的DStream操作,它通过从源DStream中的每个记录生成多个新记录来创建一个新的DStream
    // 在这种情况下,每行将被分成多个单词,单词流被表示为wordsDStream
    val words = lines.flatMap(_.split(" "))
    // 统计每批中的每个单词
    val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)
    // 每秒打印下单词的统计
    wordCounts.print()
    ssc.start()             // 开始计算
    ssc.awaitTermination()  // 等待计算终止
  }
}

3、运行

运行Netcat

nc -lk 9999

新建一个窗口运行官方例子

cd /opt/cloudera/parcels/CDH-6.3.1-1.cdh6.3.1.p0.1470567/lib/spark/

bin/run-example org.apache.spark.examples.streaming.NetworkWordCount cdh1 9999

向 9999 端口写入数据

查看Spark Streaming 窗口

Spark Streaming 会1s处理一次从9999端口来的文本数据,并进行统计

四、基本概念

1、StreamingContext

SparkContext 是 Spark 程序的入口

StreamingContext 是 Spark Streaming 程序的入口

可以从SparkConf对象创建StreamingContext对象,也可以从现有的SparkContext对象创建StreamingContext对象。定义完StreamingContext对象后必须执行如下操作:

  1. 通过创建输入DStreams来定义输入源
  2. 通过将转换和输出操作应用于DStreams来定义流计算
  3. 开始接收数据并使用streamingContext.start()进行处理。
  4. 等待处理停止(手动或由于任何错误)使用streamingContext.awaitTermination()

同样要记住以下要点:

  1. 一旦上下文启动,就不能设置或添加新的流计算
  2. 上下文一旦停止,就无法重新启动
  3. JVM中只能同时激活一个StreamingContext
  4. StreamingContext上的stop()也会停止SparkContext。要仅停止StreamingContext,请将stop()的可选参数stopSparkContext设置为false
  5. 只要在创建下一个StreamingContext之前停止前一个StreamingContext(不停止SparkContext),就可以重新使用SparkContext来创建多个StreamingContext

2、DStreams

它是Discretized Streams的简称,因此成为离散流

离散流DStream是Spark Streaming提供的基本抽象,它表示一个连续的数据流,要么是从源接收的输入数据流,要么是通过转换输入流产生的处理后的数据流。在内部,一个DStream由一系列连续的RDD表示,这是Spark对一个不可变的、分布式数据集的抽象。DStream中的每个RDD都包含来自某个区间的数据,如下图所示

在DStream上的任何操作最终都是在RDD上的操作。例如在入门例子中对单词的操作,DStream中的lines中的每个RDD应用flatMap操作以生成wordsDStream的RDD。如下图所示

这些底层RDD转换由Spark引擎计算。DStream操作隐藏了大部分细节,并为开发人员提供了更高级别的API。

3、DStreams上的输入处理

在入门例子中,lines是输入DStream,因为它表示从netcat服务器接收的数据流。每个输入DStream(除了本节后面讨论的文件流)都与一个接收器对象相关联,该对象从源接收数据并将其存储在Spark的内存中进行处理。

Spark Streaming提供了两类内置流源:

  1. 基本源:StreamingContext API中直接可用的源。示例:文件系统和套接字连接。
  2. 高级源代码:Kafka、Kinesis等源代码可通过额外的实用程序类获得。这些需要针对额外的依赖项进行链接

此外还可以自定义数据流源,此时需要自己定义一个**接收器,**它可以从自定义源接收数据并将其推送到Spark。

4、DStreams上的转换操作

与RDD类似,转换允许修改来自输入DStream的数据。DStreams支持普通Spark RDD上可用的许多转换。

一些常见的算子操作如下:

map (func )、flatMap (func )、filter (func )、repartition (numPartitions )、union (otherStream )、count ()、reduce (func )、countByValue ()、reduceByKey (func , [numTasks ])、join (otherStream , [numTasks ])、cogroup (otherStream , [numTasks ])、transform (func )、updateStateByKey (func)

其中updateStateByKey (func)我们重点说下:通过它可以操作可以保持任意状态,同时不断地用新信息更新它。

  1. 定义状态-状态可以是任意数据类型
  2. 定义状态更新函数-使用函数指定如何使用先前的状态和输入流中的新值来更新状态

在每个批处理中,Spark将对所有现有键应用状态更新函数,无论它们在批处理中是否有新数据。如果更新函数返回None,则键值对将被消除

假设我们要维护文本数据流中看到的每个单词的运行计数,在这里,运行计数是状态,它是一个整数。我们将更新函数定义为:

Scala 复制代码
def updateFunction(newValues: Seq[Int], runningCount: Option[Int]): Option[Int] = {
    val newCount = ...  // 将新值与之前的运行计数相加,得到新计数
    Some(newCount)
}

并将这个更新函数应用于入门例子中的DStream

Scala 复制代码
val runningCounts = pairs.updateStateByKey[Int](updateFunction _)

将为每个单词调用update函数,其中newValues具有1的序列,并且runningCount具有前一个count

5、DStreams上的窗口操作

Spark Streaming还提供窗口计算,允许您在数据滑动窗口上应用转换。下图说明了这个滑动窗口

如图所示,每次窗口在源DStream上滑动时,落入窗口内的源RDD被组合并操作以生成窗口化DStream的RDD。在这种情况下,操作应用于数据的最后3个时间单位,并滑动2个时间单位。这表明任何窗口操作都需要指定两个参数

  1. 窗口长度-窗口的持续时间
  2. 滑动间隔-执行窗口操作的间隔

这两个参数必须是源DStream的批处理间隔的倍数,间隔是处理数据的最小单位。

6、DStreams上的输出操作

输出操作允许将DStream的数据推送到外部系统,如数据库或文件系统。由于输出操作实际上允许转换后的数据被外部系统使用,它们触发所有DStream转换的实际执行(类似于RDD的Action算子)。目前,定义了以下输出操作:

  1. print():在运行流应用程序的驱动程序节点上打印DStream中每批数据的前十个元素。这对于开发和调试很有用。python API 中是pprint()
  2. saveAsTextFiles() :将此DStream的内容保存为文本文件。每个批处理间隔的文件名是根据前缀和后缀生成的:"前缀-TIME_IN_MS[.后缀]"
  3. saveAsObjectFiles() :将此DStream的内容保存为序列化Java对象的SequenceFiles。每个批处理间隔的文件名是根据 "prefix-TIME_IN_MS[.suffix]" *。*Python API 不适用
  4. saveAsHadoopFiles() 将此DStream的内容保存为Hadoop文件。每个批处理间隔的文件名是根据前缀和后缀生成的:"prefix-TIME_IN_MS[.suffix]"。Python API 不适用
  5. foreachRDD() :最通用的输出运算符,将函数func应用于从流生成的每个RDD。此函数应将每个RDD中的数据推送到外部系统,例如将RDD保存到文件中,或将其通过网络写入数据库。请注意,函数func在运行流应用程序的驱动程序进程中执行,并且通常会在其中包含RDD操作,这将强制计算流RDD。

7、DataFrame 和 SQL 操作

DataFrame 和 SQL在处理DStreams时同样适用。但必须使用StreamingContext正在使用的SparkContext创建SparkSession。需要将RDD转换为DataFrame,注册为临时表,然后使用SQL进行查询。如下示例:

Scala 复制代码
val words: DStream[String] = ...

words.foreachRDD { rdd =>

  // 获取SparkSession的单例实例
  val spark = SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate()
  import spark.implicits._

  // 将 RDD[String] 转换为 DataFrame
  val wordsDataFrame = rdd.toDF("word")

  // 创建临时表
  wordsDataFrame.createOrReplaceTempView("words")

  // 使用SQL对DataFrame进行字数统计并打印出来
  val wordCountsDataFrame =
    spark.sql("select word, count(*) as total from words group by word")
  wordCountsDataFrame.show()
}

8、MLlib 操作

MLlib提供的机器学习算法同样可以用到DStreams中。首先,有流式机器学习算法(例如流式线性回归、流式KMeans等),它们可以同时从流式数据中学习,并将模型应用于流式数据。除此之外,对于更大的机器学习算法类别,也可以离线学习学习模型(即使用历史数据),然后将模型在线应用于流式数据。

9、缓存和持久化

与RDD类似,DStreams也允许开发人员将流的数据持久化在内存中。也就是说,在DStream上使用persist()方法将自动将该DStream的每个RDD持久化在内存中。如果DStream中的数据将被多次计算(例如,对同一数据进行多次操作),这将非常有用。对于基于窗口的操作,如reduceByWindowreduceByKeyAndWindow和基于状态的操作,如updateStateByKey,这是隐含的。因此,基于窗口的操作生成的DStreams会自动持久化在内存中,而无需开发人员调用persist()

对于通过网络接收数据的输入流(例如Kafka、sockets等),默认持久性级别设置为将数据复制到两个节点以实现容错。

请注意,与RDD不同,DStreams的默认持久性级别将数据序列化在内存中

10、检查点

流应用程序必须全天候运行,因此必须能够抵御与应用程序逻辑无关的故障(例如,系统故障、JVM崩溃等)。为了实现这一点,Spark Streaming需要将足够的信息检查点到容错存储系统,以便它可以从故障中恢复。有两种类型的数据被检查点。

  1. 元数据检查点 *:*将定义流计算的信息保存到HDFS等容错存储中。这用于从运行流应用程序驱动程序的节点故障中恢复。元数据包括:配置、DStream操作、作业已排队但尚未完成的批次。
  2. 数据检查点:将生成的RDD保存到可靠存储中。这在一些跨多个批次组合数据的有状态转换中是必要的。在这种转换中,生成的RDD依赖于以前批次的RDD,这导致依赖链的长度随着时间不断增加。为了避免恢复时间的无限增加(与依赖链成正比),有状态转换的中间RDD被定期检查点到可靠存储(例如HDFS)以切断依赖链。

11、累加器和广播变量

累加器和广播变量无法从Spark Streaming中的检查点恢复。如果启用检查点并同时使用累加器或广播变量,则必须为累加器和广播变量创建延迟实例化的单例实例,以便在驱动程序失败时重新启动后可以重新实例化它们。例如:

Scala 复制代码
object WordExcludeList {

  @volatile private var instance: Broadcast[Seq[String]] = null

  def getInstance(sc: SparkContext): Broadcast[Seq[String]] = {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          val wordExcludeList = Seq("a", "b", "c")
          instance = sc.broadcast(wordExcludeList)
        }
      }
    }
    instance
  }
}

object DroppedWordsCounter {

  @volatile private var instance: LongAccumulator = null

  def getInstance(sc: SparkContext): LongAccumulator = {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          instance = sc.longAccumulator("DroppedWordsCounter")
        }
      }
    }
    instance
  }
}

wordCounts.foreachRDD { (rdd: RDD[(String, Int)], time: Time) =>
  // 获取或者注册 the excludeList Broadcast
  val excludeList = WordExcludeList.getInstance(rdd.sparkContext)
  // 获取或者注册 the droppedWordsCounter Accumulator
  val droppedWordsCounter = DroppedWordsCounter.getInstance(rdd.sparkContext)
  // 用 excludeList 去删除单词,用 droppedWordsCounter 统计
  val counts = rdd.filter { case (word, count) =>
    if (excludeList.value.contains(word)) {
      droppedWordsCounter.add(count)
      false
    } else {
      true
    }
  }.collect().mkString("[", ", ", "]")
  val output = "Counts at time " + time + " " + counts
})
相关推荐
黑客老李35 分钟前
面试经验分享 | 杭州某安全大厂渗透测试岗二面
大数据·服务器·数据库·经验分享·安全·面试·职场和发展
zmd-zk1 小时前
shuffle——spark
大数据·分布式·python·学习·spark
数新网络1 小时前
《深入浅出Apache Spark》系列⑤:Spark SQL的表达式优化
大数据·sql·spark
华纳云IDC服务商1 小时前
华纳云:虚拟服务器之间如何分布式运行?
分布式
dashexiaobudian2 小时前
海外媒体宣发对品牌出海的多维影响-大舍传媒
大数据·人工智能·搜索引擎·区块链
lovelin+v175030409662 小时前
智能化API:如何重塑企业业务流程与用户体验
大数据·人工智能·爬虫·python·api
SeaTunnel2 小时前
Apache SeaTunnel 集群部署详细教程
大数据
sinat_307021532 小时前
大数据法律法规——《网络安全等级保护条例》(山东省大数据职称考试)
大数据·安全
出发行进2 小时前
Hadoop其七,MapReduce的收尾,Yarn的介绍,和历史日志
大数据·hadoop·数据分析
明达技术3 小时前
MR30分布式IO模块:助力单晶炉高效生产的创新力量
运维·分布式·自动化·制造