与 RDDs 类似,转换允许修改输入 DStream 中的数据。DStreams 支持许多在普通 Spark RDD 上可用的转换算子。一些常见的转换操作定义如下:
通过函数 func 传递源 DStream 的每个元素来返回一个新的DStream。
与 map 类似,但是每个输入项可以映射到 0 或多个输出项。
通过只选择 func 返回 true 的源 DStream 的记录来返回一个新的 DStream。
通过创建更多或更少的分区来改变 DStream 中的并行度。
返回一个新的 DStream,它包含源 DStream 和otherDStream 中元素的并集。
通过计算源 DStream 的每个 RDD 中的元素数量,返回一个新的单元素 RDDs DStream。
通过使用函数 func(接受两个参数并返回一个参数)聚合原DStream 的每个 RDD 中的元素,返回一个新的单元素 RDDs DStream。这个函数应该是结合律和交换律,这样才能并行计算。
当对类型为 K 的元素的 DStream 调用时,返回一个新的 DStream (K,Long)对,其中每个键的值是它在源 DStream 的每个 RDD中的频率。
当在(K, V)对的 DStream 上调用时,返回一个新的(K, V)对的 DStream,其中每个键的值使用给定的 reduce 函数
进行聚合。
注意:
在默认情况下,这将使用 Spark 的默认并行任务数(本地模式为 2,而在集群模式下,该数量由配置属性 spark.default.parallelism决定)来进行分组。可以传递一个可选的 numTasks 参数来设置不同数量的任务。
当调用两个 DStream (K, V)和(K, W)对时,返回一个新的 DStream (K,(V, W))对,每个键的所有对的元素。
当调用(K, V)和(K, W)对的 DStream 时,返回一个新的(K, Seq[V],Seq[W])元组 DStream。
通过对源 DStream 的每个 RDD 应用一个 RDD-to-RDD函数来返回一个新的 DStream。这可以用来在 DStream 上执行任意的RDD 操作。
返回一个新的“状态”DStream,其中通过对键的前一个状态和键的新值应用给定的函数来更新每个键的状态。这可以用来维护每个键的任意状态数据。
相比RDD转换,DStream有两个特殊操作:UpdateStateByKey 操作和Window操作。这是RDD中所没有的算子,前面已经和大家说过Window操作,还有不清楚的朋友可以再去看看:
Spark Streaming 之Window操作,相信对你们会有所帮助。
那么这次就给大家说下UpdateStateByKey 操作:
updateStateByKey 操作允许维护任意状态,同时不断地用新信息更新它。要使用它,必须执行两个步骤:
在每个批处理中,Spark 将对所有现有 keys 应用状态更新功能,而不管它们在批处理中是否有新数据。如果更新函数返回 None,则键值对将被删除。
例如:
需要维护在整个文本数据流中看到的每个单词的运行计数。这里,运行计数是状态,它是一个整数。将更新函数定义为:
def updateFunction(newValues: Seq[Int],
runningCount: Option[Int]): Option[Int] = {
val newCount = ... //使用前一个运行的计数添加新值以获得新计数
Some(newCount)
}
函数调用示例:
val runningCounts = pairs.updateStateByKey[Int](updateFunction _)
完整代码如下:
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{
Seconds, StreamingContext}
/**
* 使用 Spark Streaming 处理有状态的数据
*/
object StatefulWordCount {
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("StatefulWordCount").setMaster("local[2]")
val ssc = new StreamingContext(conf, Seconds(5))
ssc.checkpoint(".")
val lines = ssc.socketTextStream("localhost", 6789)
val result = lines.flatMap(_.split(" ")).map((_, 1))
val state = result.updateStateByKey(updateFunction)
state.print()
ssc.start()
ssc.awaitTermination()
}
def updateFunction(currentValues: Seq[Int],
preValues: Option[Int]): Option[Int] = {
val curr = currentValues.sum
val pre = preValues.getOrElse(0)
Some(curr + pre)
}
}
Spark Streaming 允许 DStream 的数据被输出到外部系统,如数据库或文件系统。输出操作实际上使 transformation 操作后的数据可以被外部系统使用,同时输出操作触发所有 DStream 的 transformation 操作的实际执行(类似于 RDD的动作算子)。如下图 列出了目前主要的输出操作。
dstream.foreachRDD 是一个非常强大的输出操作,它允许将数据输出到外部系统。但是,如何正确高效地使用这个操作是很重要的,下面来讲解如何避免一些常见的错误。
通常情况下,将数据写入到外部系统需要创建一个连接对象(如 TCP 连接到远程服务器),并用它来发送数据到远程系统。出于这个目的,有的朋友可能在不经意间在 Spark Driver 端创建了连接对象,并尝试使用它保存 RDD 中的记录到 Spark Worker 上,代码如下:
dstream.foreachRDD {
rdd =>
val connection = createNewConnection() //在 Driver 上执行
rdd.foreach {
record =>
connection.send(record) // 在 Worker 上执行
}
}
这是不正确的,这需要连接对象进行序列化并从 Driver 端发送到 Worker 上。连接对象很少在不同机器间进行这种操作;
此错误可能表现为序列化错误(连接对不可序列化)、初始化错误(连接对象需要在 Worker 上进行初始化)等,正确的解决办法是在 Worker 上创建连接对象。
通常情况下,创建一个连接对象有时间和资源开销。因此,创建和销毁的每条记录的连接对象都可能会导致不必要的资源开销,并显著降低系统整体的吞吐量。
一个比较好的解决方案是使用 rdd.foreachPartition 方法创建一个单独的连接对象,然后将该连接对象输出的所有 RDD 分区中的数据使用到外部系统。
还可以进一步通过在多个 RDDs/batch 上重用连接对象进行优化。一个保持连接对象的静态池可以重用在多个批处理的 RDD 上,从而进一步降低了开销。
需要注意的是,在静态池中的连接应该按需延迟创建,这样可以更有效地把数据发送到外部系统。
另外需要要注意的是,DStream 是延迟执行的,就像 RDD 的操作是由 Actions 触发一样。默认情况下,输出操作会按照它们在 Streaming 应用程序中定义的顺序逐个执行。