基本概念
DataFrame和SQL操作
您可以轻松地对流数据使用DataFrames和SQL进行操作。您必须使用StreamingContext使用的SparkContext创建一个SparkSession对象。此外,必须这样做,以便可以在驱动程序发生故障时重新启动。这是通过创建一个延迟实例化的SparkSession单例实例来完成的。在下面的示例中显示。它修改了前面的单词计数示例,以使用DataFrames和SQL生成单词计数。每个RDD都转换为一个DataFrame,注册为临时表,然后使用SQL查询。
/** DataFrame operations inside your streaming program */
val words: DStream[String] = ...
words.foreachRDD { rdd =>
// Get the singleton instance of SparkSession
val spark = SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate()
import spark.implicits._
// Convert RDD[String] to DataFrame
val wordsDataFrame = rdd.toDF("word")
// Create a temporary view
wordsDataFrame.createOrReplaceTempView("words")
// Do word count on DataFrame using SQL and print it
val wordCountsDataFrame =
spark.sql("select word, count(*) as total from words group by word")
wordCountsDataFrame.show()
}
查看完整的源代码。
您还可以在来自不同线程的流数据上定义的表上执行SQL查询(即与正在运行的StreamingContext异步)。只要确保您将StreamingContext设置为记住足够的流数据即可运行查询。否则,不知道任何异步SQL查询的StreamingContext将在查询完成之前删除旧的流数据。例如,如果您要查询最后一批,但是查询可能需要5分钟才能运行,请调用streamingContext.remember(Minutes(5))
(使用Scala或其他语言的等效语言)。
请参阅DataFrames和SQL指南以了解有关DataFrames的更多信息。
MLlib操作
您还可以轻松使用MLlib提供的机器学习算法。首先,有流式机器学习算法(例如,流线性回归,流KMeans等),它们可以同时从流数据中学习并将模型应用于流数据。除此之外,对于更大范围的机器学习算法,您可以离线学习学习模型(即使用历史数据),然后在线将模型应用于流数据。有关更多详细信息,请参见MLlib指南。
缓存/持久化
与RDD类似,DStream也允许开发者将流数据持久化在内存中。也就是说,在DStream上使用persist()
方法将自动将该DStream的每个RDD持久化在内存中。如果DStream中的数据将被多次计算(例如,对同一数据进行多次操作),这将非常有用。对于基于窗口的操作(如reduceByWindow和reduceByKeyAndWindow)以及基于状态的操作(如updateStateByKey),这是隐式进行了持久化。因此,由基于窗口的操作生成的DStream会自动保存在内存中,而无需开发人员调用persist()
方法。
对于通过网络接收数据的输入流(例如Kafka,Flume,套接字等),默认的持久化级别设置为将数据复制到两个节点以实现容错。
注意,与RDD不同,DStream的默认持化级别是将数据序列化在内存中。性能调优部分将对此进行进一步讨论。有关不同持久化级别的更多信息,请参见《 Spark编程指南》。
检查点
流式应用程序必须7*24小时运行,因此必须对与应用程序逻辑无关的故障(例如系统故障,JVM崩溃等)具有弹性。为此,Spark Streaming需要向容错存储系统检查足够的信息,以便可以从故障中恢复。有两种类型数据的检查点。
- 元数据检查点-将定义流计算的信息保存到HDFS等容错存储系统中。这用于从运行流应用程序的驱动程序的节点的故障中恢复(稍后详细讨论)。元数据包括:
- 配置-用于创建流应用程序的配置。
- DStream操作-定义流应用程序的DStream操作集。
- 不完整的批次-作业对列尚未完成的批次。
- 数据检查点-将生成的RDD保存到可靠的存储中。在某些状态转换中对数据进行检查点是有必要的,这些转换将多个批次中的数据合并在一起。在这种转换中,生成的RDD依赖于先前批次的RDD,这导致依赖项链的长度随着时间而不断增加。为了避免恢复时间的无限增加(与依赖关系成正比)。状态转换的中间RDD定期检查点到可靠的存储系统中(例如HDFS),以切断依赖链。
总而言之,从驱动程序故障中恢复时,主要需要元数据检查点,而如果使用有状态转换,则即使是基本功能,也需要数据或RDD检查点。
何时启用检查点
必须为具有以下任何要求的应用程序启用检查点:
- 有状态转换的用法 - 如果在应用程序中使用了updateStateByKey或reduceByKeyAndWindow(带有反函数),则必须提供检查点目录以允许定期的RDD检查点。
- 从运行应用程序的驱动程序故障中恢复-元数据检查点用于恢复的进度信息。
请注意,没有上述状态转换的简单流应用程序可以在不启用检查点的情况下运行。在这种情况下,从驱动程序故障中恢复也将是部分的(某些已接收但未处理的数据可能会丢失)。这通常是可以接受的,并且许多都以这种方式运行Spark Streaming应用程序。预计将来会改善对非Hadoop环境的支持。
如何配置检查点
可以通过在容错,可靠的文件系统(例如HDFS,S3等)中设置目录来启用检查点,将检查点信息保存到该目录中。这是通过使用streamingContext.checkpoint(checkpointDirectory)
完成的。这将允许您使用上述有状态转换。此外,如果要使应用程序从驱动程序故障中恢复,则应重写流应用程序以具有以下行为。
- 程序首次启动时,它将创建一个新的StreamingContext,设置所有流,然后调用
start()
。 - 失败后重新启动程序时,它将根据检查点目录中的检查点数据重新创建StreamingContext。
通过使用StreamingContext.getOrCreate
,此行为变得很简单。用法如下:
// Function to create and setup a new StreamingContext
def functionToCreateContext(): StreamingContext = {
val ssc = new StreamingContext(...) // new context
val lines = ssc.socketTextStream(...) // create DStreams
...
ssc.checkpoint(checkpointDirectory) // set checkpoint directory
ssc
}
// Get StreamingContext from checkpoint data or create a new one
val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _)
// Do additional setup on context that needs to be done,
// irrespective of whether it is being started or restarted
context. ...
// Start the context
context.start()
context.awaitTermination()
如果checkpointDirectory目录存在,则将根据检查点数据重新创建上下文。如果该目录不存在(即首次运行),则将调用函数functionToCreateContext来创建新上下文并设置DStreams。请参阅Scala示例RecoverableNetworkWordCount。本示例将网络数据的字数追加到文件中。
除了使用getOrCreate之外,还需要确保驱动程序进程在发生故障时自动重新启动。这只能由用于运行应用程序的部署基础结构来完成。这将在“部署”部分中进一步讨论。
注意,RDD的检查点会导致保存到可靠存储系统的成本。这可能会导致RDD获得检查点的那些批次的处理时间增加。因此,需要谨慎设置检查点的间隔时间。在小批次(例如间隔时间为1秒)时,每个批次的检查点可能会大大降低操作吞吐量。相反,检查点太少会导致血统和任务规模增加,这可能会产生不利影响。对于需要RDD检查点的有状态转换,默认间隔为批处理间隔的倍数,至少应为10秒。可以使用dstream.checkpoint(checkpointInterval)
进行设置。通常,DStream的5-10个滑动间隔的检查点间隔是一个很好的尝试设置。
累加器,广播变量和检查点
无法从Spark Streaming中的检查点恢复累加器和广播变量。如果启用检查点并同时使用Accumulators或Broadcast变量,则必须为Accumulators和Broadcast变量创建延迟实例化的单例实例,以便在驱动程序故障重启后可以重新实例化它们。如下面的示例中所示:
object WordBlacklist {
@volatile private var instance: Broadcast[Seq[String]] = null
def getInstance(sc: SparkContext): Broadcast[Seq[String]] = {
if (instance == null) {
synchronized {
if (instance == null) {
val wordBlacklist = Seq("a", "b", "c")
instance = sc.broadcast(wordBlacklist)
}
}
}
instance
}
}
object DroppedWordsCounter {
@volatile private var instance: LongAccumulator = null
def getInstance(sc: SparkContext): LongAccumulator = {
if (instance == null) {
synchronized {
if (instance == null) {
instance = sc.longAccumulator("WordsInBlacklistCounter")
}
}
}
instance
}
}
wordCounts.foreachRDD { (rdd: RDD[(String, Int)], time: Time) =>
// Get or register the blacklist Broadcast
val blacklist = WordBlacklist.getInstance(rdd.sparkContext)
// Get or register the droppedWordsCounter Accumulator
val droppedWordsCounter = DroppedWordsCounter.getInstance(rdd.sparkContext)
// Use blacklist to drop words and use droppedWordsCounter to count them
val counts = rdd.filter { case (word, count) =>
if (blacklist.value.contains(word)) {
droppedWordsCounter.add(count)
false
} else {
true
}
}.collect().mkString("[", ", ", "]")
val output = "Counts at time " + time + " " + counts
})
查看完整的源代码。
部署应用程序
本章节将讨论部署Spark Streaming应用程序的步骤。
要求
要运行Spark Streaming应用程序,您需要具备以下条件。
- 使用集群管理器管理的集群 - 这是任何Spark应用程序的基本要求,并且在部署指南中进行了详细讨论。
- 将应用程序打成JAR包 - 您必须将流式应用程序编译成JAR包。如果您正在使用spark-submit来启动应用程序,则无需提供Spark和Spark Streaming的JAR包。但是,如果您的应用程序使用高级输入源(例如Kafka,Flume),则必须将它们添加额外的依赖及其依赖项打包在用于部署应用程序的JAR中。例如,使用KafkaUtils的应用程序必须在应用程序JAR中包含spark-streaming-kafka-0-8_2.11及其所有相关的依赖项。
- 为执行程序配置足够的内存-由于接收到的数据必须存储在内存中,因此必须为执行程序配置足够的内存来保存接收到的数据。请注意,如果您要执行10分钟的窗口操作,则系统必须在内存中至少保留最后10分钟的数据。因此,应用程序的内存的大小要求取决于其中使用的操作。
- 配置检查点-如果流应用程序需要配置检查点,则必须将Hadoop API兼容的容错存储中的目录(例如HDFS,S3等)配置为检查点目录,并且以这样的方式编写流应用程序:用于故障恢复。有关更多详细信息,请参见检查点部分。
- 配置应用程序驱动程序的自动重启-要从驱动程序故障中自动恢复,用于运行流式应用程序的部署基础结构必须监控驱动程序进程,并在驱动程序失败时重新启动。不同的集群管理器具有不同的工具来实现:
- Spark Standalone-可以提交Spark应用程序驱动程序以Spark Standalone集群模式运行(请参阅集群部署模式),即应用程序驱动程序本身在worker节点之一上运行。此外,可以指示独立集群管理器监控驱动程序,并在驱动程序由于非零退出代码或由于运行该驱动程序的节点故障而失败时重新启动它。有关更多详细信息,请参见Spark Standalone指南中的集群模式和监控。
- YARN-Yarn支持自动重启应用程序的类似机制。请参阅YARN文档以获取更多详细信息。
- Mesos-Marathon已经用Mesos来实现这一功能。
- 配置预写日志-自Spark 1.2起,我们引入了预写日志以实现强大的容错保证。如果启用,则将从接收器接收的所有数据写入配置检查点目录中的预写日志中。这样可以防止驱动程序恢复时丢失数据,从而确保零数据丢失(在“容错语义”部分中进行了详细讨论 )。这可以通过设置来启用配置参数
spark.streaming.receiver.writeAheadLog.enable
为true
。但是,这些更强的语义可能以单个接收器的接收吞吐量为代价。可以通过并行运行更多接收器来纠正此问题增加总吞吐量。另外,由于启用了预写日志,因此建议禁用Spark中接收到的数据的复制,因为该日志已存储在复制的存储系统中。可以通过将输入流的存储级别设置为来完成此操作StorageLevel.MEMORY_AND_DISK_SER
。在使用S3(或任何不支持刷新的文件系统)作为预写日志时,请记住启用spark.streaming.driver.writeAheadLog.closeFileAfterWrite
和spark.streaming.receiver.writeAheadLog.closeFileAfterWrite
。有关更多详细信息,请参见Spark Streaming配置。请注意,启用I/O加密后,Spark不会加密写入预写日志的数据。如果需要对预写日志数据进行加密,则应将其存储在本机支持加密的文件系统中。 - 设置最大接收速率-如果群集资源不足以使流式应用程序以最快的速度处理数据,则可以通过设置记录/秒的最大速率限制来限制接收器的速率。请参阅接收器和 Direct Kafka方法的配置参数。在Spark 1.5中,我们引入了一项称为背压的功能,该功能消除了设置此速率限制的需要,因为Spark Streaming会自动计算出速率限制,并在处理条件发生变化时动态调整它们。这个背压可以通过设置来启用配置参数
spark.streaming.backpressure.enabled
为true
。
升级应用程序代码
如果需要使用新的应用程序代码升级正在运行的Spark Streaming应用程序,则有两种可能的机制。
- 升级后的Spark Streaming应用程序将启动,并与现有应用程序并行运行。一旦新的(接收的数据与旧的数据相同)已经准备好并且准备好合适的时间,则可以将旧的数据降下来。请注意,对于支持将数据发送到两个目标的数据源(即较早的和升级的应用程序),可以这样做。
- 优雅停止现有应用程序(有关优雅停止选项,请参见
StreamingContext.stop(...)
或JavaStreamingContext.stop(...)
),以确保在关闭之前已完全处理已接收的数据。然后可以启动升级的应用程序,它将从较早应用程序停止的同一点开始处理。请注意,只有使用支持在输入源端缓冲的输入源(例如Kafka和Flume)才能完成此操作,因为在上一个应用程序关闭且升级后的应用程序尚未启动时需要缓冲数据。并且无法从升级前代码的较早检查点信息重新启动。检查点信息本质上包含序列化的Scala/Java/Python对象,尝试使用经过修改的新类反序列化对象可能会导致错误。在这种情况下,要么使用其他检查点目录启动升级的应用程序,要么删除先前的检查点目录。
监控应用程序
除了Spark的监控功能外,Spark Streaming还具有其他特定功能。使用StreamingContext时,Spark Web UI会显示一个附加的Streaming选项卡,其中显示有关正在运行的接收器(接收器是否处于活动状态,接收到的记录数,接收器错误等)和已完成的批处理(批处理时间,排队延迟等)的统计信息 )。这可用于监视流应用程序的进度。
Web UI中的以下两个指标特别重要:
处理时间-处理每批数据的时间
调度延迟-批处理在队列中等待先前批处理完成的时间
如果批处理时间始终大于批处理时间间隔或排队延迟持续增加,则表明系统无法像生成批处理一样快处理批处理,并且处于落后。在这种情况下,请考虑减少批处理时间。
还可以使用StreamingListener界面监控Spark Streaming程序的进度,该界面可让您获取接收器状态和处理时间。请注意,这是一个开发人员API,将来可能会得到改进(即报告了更多信息)。
公众号ID:ldc11235 扫码关注最新动态,跟我一起学大数据