我们可以轻松地对流数据使用DataFrames和SQL操作。我们必须使用StreamingContext正在使用地SparkContext创建SparkSession。此外,必须如此,才可以在驱动器故障时重新启动。这是通过创建一个延迟实例化地SparkSession单实例来完成的。这在下示例中得以展示。
/** Java Bean class for converting RDD to DataFrame */
public class JavaRow implements java.io.Serializable {
private String word;
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
}
...
/** DataFrame operations inside your streaming program */
JavaDStream words = ...
words.foreachRDD((rdd, time) -> {
// Get the singleton instance of SparkSession
SparkSession spark = SparkSession.builder().config(rdd.sparkContext().getConf()).getOrCreate();
// Convert RDD[String] to RDD[case class] to DataFrame
JavaRDD rowRDD = rdd.map(word -> {
JavaRow record = new JavaRow();
record.setWord(word);
return record;
});
DataFrame wordsDataFrame = spark.createDataFrame(rowRDD, JavaRow.class);
// Creates a temporary view using the DataFrame
wordsDataFrame.createOrReplaceTempView("words");
// Do word count on table using SQL and print it
DataFrame wordCountsDataFrame =
spark.sql("select word, count(*) as total from words group by word");
wordCountsDataFrame.show();
});
我们还可以对从不同线程(即,与正在运行的StreamingContext异步)的流数据上定义的表运行SQL查询。只需要确保将StreamingContext设置为记住足够数量的流数据,以便查询可以运行。否则,不知道任何异步SQL查询的StreamingContext将在查询完成之前删除旧的流数据。例如,如果要查询最后一批,但查询可能需要5分钟才能运行,则调用streamingContext.remember(Minutes(5))
我们还可以轻松使用MLlib提供的机器学习算法。首先,有流媒体机器学习算法(例如流媒体线性回归,流媒体KMeans等),它们可以同时学习流数据以及将模型应用于流数据。除此之外,对于更大类的机器学习算法,我们可以离线学习学习模型(即使用历史数据),然后在线将数据应用于流数据。
与RDD类似,DStreams还允许开发人员将流的数据保存在内存中。也就是说,persist()在DStream上使用该方法会自动将该DStream的每个RDD保留在内存中。如果DStream中的数据将被多次计算(例如,对相同数据进行多次操作),这将非常有用。对于像reduceByWindow、reduceByKeyAndWindow这样的基于窗口操作和updateStateByKey这样基于状态的操作,这是隐含的。因此,基于窗口的操作生成的DStream会自动保留在内存中,而无需开发人员调用persist()。
对于通过网络接收数据的输入流(例如,Kafka,Flume,Socket等),默认持久性级别设置为将数据复制到两个节点以实现容错。
请注意,与RDD不同,DStream的默认持久性级别会将数据序列化为内存。
流应用程序必须全天候运行,因此必须能够适应与应用程序逻辑无关的故障(例如,系统故障,JVM崩溃等)。为了实现这一点,Spark Streaming需要将足够的信息检查到容错存储系统,以便它可以从故障中恢复。检查点有两种类型的数据。
总而言之,元数据检查点主要用于从驱动程序故障中恢复,而如果使用状态转换,即使对于基本功能也需要数据或RDD检查点。
必须为具有以下任何要求地应用程序启用检查点:
请注意,可以在不启用检查点的情况下运行没有上述有状态转换的简单流应用程序。在这种情况下,驱动程序故障的恢复也将是部分的(某些已接收但未处理的数据可能会丢失)。这通常是可以接受的,并且许多以这种方式运行Spark Streaming应用程序。
可以通过在容错,可靠的文件系统(例如,HDFS,S3等)中设置目录来启用检查点,检查点信息将保存到该文件系统中。这是通过使用完成的streamingContext.checkpoint(checkpointDirectory)。这将允许我们使用上述有状态转换。此外,如果要使应用程序从驱动程序故障中恢复,则应重写流应用程序以使其具有以下行为。
// Create a factory object that can create and setup a new JavaStreamingContext
JavaStreamingContextFactory contextFactory = new JavaStreamingContextFactory() {
@Override public JavaStreamingContext create() {
JavaStreamingContext jssc = new JavaStreamingContext(...); // new context
JavaDStream lines = jssc.socketTextStream(...); // create DStreams
...
jssc.checkpoint(checkpointDirectory); // set checkpoint directory
return jssc;
}
};
// Get JavaStreamingContext from checkpoint data or create a new one
JavaStreamingContext context = JavaStreamingContext.getOrCreate(checkpointDirectory, contextFactory);
// 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存在,则将从检查点数据重新创建上下文。如果目录不存在(即第一次运行),则将contextFactory调用该函数以创建新上下文并设置DStream。
除了使用getOrCreate之外还需要确保驱动程序进程在失败时自动重启。这只能通过用于运行应用程序的部署基础结构来完成。
请注意,RDD的检查点会导致节省可靠存储的成本。这可能会导致RDD被检查点的哪些批次的处理时间增加。因此,需要仔细设置检查点的间隔。在小批量(例如1秒)时,每批次的检查点可能会显著降低操作吞吐量。相反,检查点过于频繁会导致谱系和任务大小增长,这可能会产生不利影响。对于需要RDD检查点的有状态转换,默认间隔是批处理间隔的倍数,至少为10秒。它可以通过使用dstream.checkpoint(checkpointInterval)来设置。通常,DStream的5-10个滑动间隔的检查点间隔是一个很好的设置。
无法从Spark Streaming中的检查点恢复累加器和广播变量。如果启用了检查点并使用累加器或广播变量,则必须为累加器和广播变量创建延迟实例化的单实例,以便在驱动程序重新启动失败后重新实例化它们。示例如下:
class JavaWordBlacklist {
private static volatile Broadcast> instance = null;
public static Broadcast> getInstance(JavaSparkContext jsc) {
if (instance == null) {
synchronized (JavaWordBlacklist.class) {
if (instance == null) {
List wordBlacklist = Arrays.asList("a", "b", "c");
instance = jsc.broadcast(wordBlacklist);
}
}
}
return instance;
}
}
class JavaDroppedWordsCounter {
private static volatile LongAccumulator instance = null;
public static LongAccumulator getInstance(JavaSparkContext jsc) {
if (instance == null) {
synchronized (JavaDroppedWordsCounter.class) {
if (instance == null) {
instance = jsc.sc().longAccumulator("WordsInBlacklistCounter");
}
}
}
return instance;
}
}
wordCounts.foreachRDD((rdd, time) -> {
// Get or register the blacklist Broadcast
Broadcast> blacklist = JavaWordBlacklist.getInstance(new JavaSparkContext(rdd.context()));
// Get or register the droppedWordsCounter Accumulator
LongAccumulator droppedWordsCounter = JavaDroppedWordsCounter.getInstance(new JavaSparkContext(rdd.context()));
// Use blacklist to drop words and use droppedWordsCounter to count them
String counts = rdd.filter(wordCount -> {
if (blacklist.value().contains(wordCount._1())) {
droppedWordsCounter.add(wordCount._2());
return false;
} else {
return true;
}
}).collect().toString();
String output = "Counts at time " + time + " " + counts;
}