Spark Streaming转换操作

在流计算应用场景中,数据流会源源不断到达,Spark Streaming会把连续的数据流切分成一个又一个分段,然后,对每个分段内的DStream数据进行处理,也就是对DStream进行各种转换操作,包括无状态转换操作有状态转换操作。

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

Transformation

Meaning

map(func)

将源DStream中的每个元素通过一个函数func从而得到新的DStreams。

flatMap(func)

和map类似,但是每个输入的项可以被映射为0或更多项。

filter(func)

选择源DStream中函数func判为true的记录作为新DStreams 

repartition(numPartitions)

通过创建更多或者更少的partition来改变此DStream的并行级别。

union(otherStream)

联合源DStreams和其他DStreams来得到新DStream 

count()

统计源DStreams中每个RDD所含元素的个数得到单元素RDD的新DStreams。

reduce(func)

通过函数func(两个参数一个输出)来整合源DStreams中每个RDD元素得到单元素RDD的DStreams。这个函数需要关联从而可以被并行计算。

countByValue()

对于DStreams中元素类型为K调用此函数,得到包含(K,Long)对的新DStream,其中Long值表明相应的K在源DStream中每个RDD出现的频率。

reduceByKey(func, [numTasks])

对(K,V)对的DStream调用此函数,返回同样(K,V)对的新DStream,但是新DStream中的对应V为使用reduce函数整合而来。Note:默认情况下,这个操作使用Spark默认数量的并行任务(本地模式为2,集群模式中的数量取决于配置参数spark.default.parallelism)。你也可以传入可选的参数numTaska来设置不同数量的任务。 

join(otherStream, [numTasks])

两DStream分别为(K,V)和(K,W)对,返回(K,(V,W))对的新DStream。 

cogroup(otherStream, [numTasks])

两DStream分别为(K,V)和(K,W)对,返回(K,(Seq[V],Seq[W])对新DStreams 

transform(func)

将RDD到RDD映射的函数func作用于源DStream中每个RDD上得到新DStream。这个可用于在DStream的RDD上做任意操作。 

updateStateByKey(func)

得到”状态”DStream,其中每个key状态的更新是通过将给定函数用于此key的上一个状态和新值而得到。这个可用于保存每个key值的任意状态数据。 

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

• 在无状态转化操作中,每个批次的处理不依赖于之前批次的数据。常见的 RDD 转化操作,例如 map()、filter()、reduceByKey() 等,都是无状态转化操作。

• 相对地,有状态转化操作需要使用之前批次的数据或者是中间结果来计算当前批次的数据。有状态转化操作包括基于滑动窗口的转化操作和追踪状态变化的转化操作。

无状态转化操作

 无状态转化操作就是把简单的 RDD 转化操作应用到每个批次上,也就是转化 DStream 中的每一个 RDD。部分无状态转化操作列在了下表中。 注意,针对键值对的 DStream 转化操作(比如 reduceByKey())要添加import StreamingContext._ 才能在 Scala中使用。

需要记住的是,尽管这些函数看起来像作用在整个流上一样,但事实上每个 DStream 在内部是由许多 RDD(批次)组成,且无状态转化操作是分别应用到每个 RDD 上的。例如, reduceByKey() 会归约每个时间区间中的数据,但不会归约不同区间之间的数据。

举个例子,在之前的wordcount程序中,我们只会统计1秒内接收到的数据的单词个数,而不会累加。 

无状态转化操作也能在多个 DStream 间整合数据,不过也是在各个时间区间内。例如,键 值对 DStream 拥有和 RDD 一样的与连接相关的转化操作,也就是 cogroup()、join()、 leftOuterJoin() 等。我们可以在 DStream 上使用这些操作,这样就对每个批次分别执行了对应的 RDD 操作。

我们还可以像在常规的 Spark 中一样使用 DStream 的 union() 操作将它和另一个 DStream 的内容合并起来,也可以使用 StreamingContext.union() 来合并多个流。

有状态转化操作

DStream有状态转换操作包括滑动操作和updateStateByKey操作。

滑动窗口转换操作

Window Operations有点类似于Storm中的State,可以设置窗口的大小和滑动窗口的间隔来动态的获取当前Steaming的允许状态。

基于窗口的操作会在一个比 StreamingContext 的批次间隔更长的时间范围内,通过整合多个批次的结果,计算出整个窗口的结果。

所有基于窗口的操作都需要两个参数,分别为窗口时长以及滑动步长,两者都必须是 StreamContext 的批次间隔的整数倍。窗口时长控制每次计算最近的多少个批次的数据,其实就是最近的 windowDuration/batchInterval 个批次。如果有一个以 10 秒为批次间隔的源 DStream,要创建一个最近 30 秒的时间窗口(即最近 3 个批次),就应当把 windowDuration 设为 30 秒。而滑动步长的默认值与批次间隔相等,用来控制对新的 DStream 进行计算的间隔。如果源 DStream 批次间隔为 10 秒,并且我们只希望每两个批次计算一次窗口结果, 就应该把滑动步长设置为 20 秒。

Transformation

Meaning

window(windowLengthslideInterval)

基于对源DStream窗化的批次进行计算返回一个新的DStream

countByWindow(windowLengthslideInterval)

返回一个滑动窗口计数流中的元素。

reduceByWindow(funcwindowLengthslideInterval)

通过使用自定义函数整合滑动区间流元素来创建一个新的单元素流。

reduceByKeyAndWindow(funcwindowLengthslideInterval, [numTasks])

当在一个(K,V)对的DStream上调用此函数,会返回一个新(K,V)对的DStream,此处通过对滑动窗口中批次数据使用reduce函数来整合每个key的value值。Note:默认情况下,这个操作使用Spark的默认数量并行任务(本地是2),在集群模式中依据配置属性(spark.default.parallelism)来做grouping。你可以通过设置可选参数numTasks来设置不同数量的tasks。 

reduceByKeyAndWindow(funcinvFuncwindowLengthslideInterval, [numTasks])

这个函数是上述函数的更高效版本,每个窗口的reduce值都是通过用前一个窗的reduce值来递增计算。通过reduce进入到滑动窗口数据并”反向reduce”离开窗口的旧数据来实现这个操作。一个例子是随着窗口滑动对keys的“加”“减”计数。通过前边介绍可以想到,这个函数只适用于”可逆的reduce函数”,也就是这些reduce函数有相应的”反reduce”函数(以参数invFunc形式传入)。如前述函数,reduce任务的数量通过可选参数来配置。注意:为了使用这个操作,检查点必须可用。 

countByValueAndWindow(windowLength,slideInterval, [numTasks])

对(K,V)对的DStream调用,返回(K,Long)对的新DStream,其中每个key的值是其在滑动窗口中频率。如上,可配置reduce任务数量。

reduceByWindow() 和 reduceByKeyAndWindow() 让我们可以对每个窗口更高效地进行归约操作。它们接收一个归约函数,在整个窗口上执行,比如 +。除此以外,它们还有一种特殊形式,通过只考虑新进入窗口的数据和离开窗 口的数据,让 Spark 增量计算归约结果。这种特殊形式需要提供归约函数的一个逆函数,比 如 + 对应的逆函数为 -。对于较大的窗口,提供逆函数可以大大提高执行效率  

val ipDStream = accessLogsDStream.map(logEntry => (logEntry.getIpAddress(), 1))
val ipCountDStream = ipDStream.reduceByKeyAndWindow(

  {(x, y) => x + y},
  {(x, y) => x - y},
  Seconds(30),
  Seconds(10))

追踪状态变化UpdateStateByKey

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

updateStateByKey() 的结果会是一个新的 DStream,其内部的 RDD 序列是由每个时间区间对应的(键,状态)对组成的。

updateStateByKey操作使得我们可以在用新信息进行更新时保持任意的状态。为使用这个功能,你需要做下面两步: 
1. 定义状态,状态可以是一个任意的数据类型。 
2. 定义状态更新函数,用此函数阐明如何使用之前的状态和来自输入流的新值对状态进行更新。

使用updateStateByKey需要对检查点目录进行配置,会使用检查点来保存状态。

下面就用一个案例来演示下

在"/usr/local/spark/mycode/streaming/stateful"目录下新建一个代码文件NetworkWordCountStateful.scala,输入代码如下所示:

package org.apache.spark.examples.streaming
import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.storage.StorageLevel
object NetworkWordCountStateful {
  def main(args: Array[String]) {
    //定义状态更新函数
    val updateFunc = (values: Seq[Int], state: Option[Int]) => {
      val currentCount = values.foldLeft(0)(_ + _)
      val previousCount = state.getOrElse(0)
      Some(currentCount + previousCount)
    }
      StreamingExamples.setStreamingLogLevels()  //设置log4j日志级别
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCountStateful")
    val sc = new StreamingContext(conf, Seconds(5))
    sc.checkpoint("file:///usr/local/spark/mycode/streaming/stateful/")    //设置检查点,检查点具有容错机制
    //定义了一个"套接字流"类型的数据源,这个数据源可以用nc程序产生。需要注意的是,在代码中,已经确定了Socket客户端会向主机名为localhost的9999端口号发起Socket通信请求
    val lines = sc.socketTextStream("localhost", 9999)
    val words = lines.flatMap(_.split(" "))
    val wordDstream = words.map(x => (x, 1))
    val stateDstream = wordDstream.updateStateByKey[Int](updateFunc)
    stateDstream.print()
    sc.start()
    sc.awaitTermination()
  }
}

然后新建一个StreamingExamples.scala文件,代码如下所示:

package org.apache.spark.examples.streaming 
import org.apache.spark.internal.Logging 
import org.apache.log4j.{Level, Logger}
/** Utility functions for Spark Streaming examples. */
object StreamingExamples extends Logging {
  /** Set reasonable logging levels for streaming if the user has not configured log4j. */
  def setStreamingLogLevels() {
    val log4jInitialized = Logger.getRootLogger.getAllAppenders.hasMoreElements
    if (!log4jInitialized) {
      // We first log something to initialize Spark's default logging, then we override the
      // logging level.
      logInfo("Setting log level to [WARN] for streaming example." +
        " To override add a custom log4j.properties to the classpath.")
      Logger.getRootLogger.setLevel(Level.WARN)
    }
  }
}

在"/usr/local/spark/mycode/streaming/stateful/"目录下新建一个simple.sbt文件,然后,使用sbt工具进行编译打包。打包成功后,在Linux终端执行如下命令提交运行程序:

/usr/local/spark/bin/spark-submit \
--class "org.apache.spark.examples.streaming.NetworkWordCountStateful" \
./target/scala-2.11/simple-project_2.11-1.0.jar

 执行上述命令后,NetworkWordCountStateful程序就启动了,它会向主机名为localhost的9999号发起socket通信请求。这里我们让nc程序扮演Socket服务器端,也就是让NetworkWordCountStateful程序和nc程序建立Socket连接。一旦Socket连接,NetworkWordCountStateful程序接收来自nc程序的数据,并进行词频统计。打开一个新的终端,输入如下命令:

nc -lk 9999

在这个窗口手动输入一些单词

切换到刚才流计算终端,可以看到已经输出了类似如下的词频统计信息: 

 从上面的词频统计可以看出,Spark Streaming每隔5秒执行一次词频统计,并且每次词频统计都包含了历史的词频统计信息。

发布了85 篇原创文章 · 获赞 39 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_41338249/article/details/90344594