一旦定义了最终结果DataFrame/Dataset,剩下的的就是开始流式计算。为此,我们必须使用Dataset.writeStream()方法返回的的DataStreamWriter。我们必须在此界面中指定以下一项或多项参数。
有几种类型的输出模式:
不同类型的流式查询支持不同的输出模式。这是兼容性矩阵:
查询类型 | 支持的输出模式 | 笔记 | |
具有聚合的查询 | 事件时间与水印的聚合 | 追加,更新,完成 | 追加模式使用水印来删除旧的聚合状态。但是窗口聚合的输出延迟了'withWatermark()'中指定的后期阈值,如模式语义,在结束后(即超过水印后)行只能添加到结果表中一次。 |
其他聚合 | 完成,更新 | 由于未定义水印(仅在其他类别中定义),因此不会丢弃旧的聚合状态。 不支持追加模式,因为聚合可以更新,因此违反了此模式的语义。 |
|
查询mapGroupsWithState | 更新 | ||
查询flatMapGroupsWithState | 追加操作模式 | 追加 | 之后允许聚合flatMapGroupsWithState。 |
更新操作模式 | 更新 | 之后不允许聚合flatMapGroupsWithState。 | |
查询joins | 追加 | 尚不支持更新和完成模式。 | |
其他查询 | 追加,更新 | 不支持完成模式,因为在结果表中保留所有未聚合数据是不可行的。 |
有集中类型的内置输出接收器:
writeStream
.format("parquet") // can be "orc", "json", "csv", etc.
.option("path", "path/to/destination/dir")
.start()
writeStream
.format("kafka")
.option("kafka.bootstrap.servers", "host1:port1,host2:port2")
.option("topic", "updates")
.start()
writeStream
.foreach(...)
.start()
writeStream
.format("console")
.start()
writeStream
.format("memory")
.queryName("tableName")
.start()
某些接收器不具有容错能力,因为它们不保证输出的持久性,仅用于调试目的,以下时Spark中所有接收器的详细信息:
Sink | 支持输出的模式 | 选项 | 容错 | 笔记 |
文件接收器 | Append | path:必须指定输出目录的路径 | 是(只有一次) | 支持写入分区表。按时间划分可能很有用。 |
Kafka Sink | Append,Update,Complete | 是(至少一次) | ||
Foreach Sink |
Append,Update,Complete | 无 | 取决于ForeachWriter的实现 | |
控制台接收器 | Append,Update,Complete | numRows:每次触发器打印的行数(默认值:20) truncate:是否过长时截断输出(默认值:true) |
没有 | |
内存接收器 | Append,Complete | 没有 | 不是。但在Complete模式下,重新启动的查询将重新创建完整的表 | 表名是查询名称。 |
请注意,我们必须调用start()方法才能开始执行查询。这将返回一个StreamingQuery对象,该对象是持续运行的执行句柄。我们可以使用此对象来管理查询。示例:
// ========== DF with no aggregations ==========
Dataset noAggDF = deviceDataDf.select("device").where("signal > 10");
// Print new data to console
noAggDF
.writeStream()
.format("console")
.start();
// Write new data to Parquet files
noAggDF
.writeStream()
.format("parquet")
.option("checkpointLocation", "path/to/checkpoint/dir")
.option("path", "path/to/destination/dir")
.start();
// ========== DF with aggregation ==========
Dataset aggDF = df.groupBy("device").count();
// Print updated aggregations to console
aggDF
.writeStream()
.outputMode("complete")
.format("console")
.start();
// Have all the aggregates in an in-memory table
aggDF
.writeStream()
.queryName("aggregates") // this query name will be the table name
.outputMode("complete")
.format("memory")
.start();
spark.sql("select * from aggregates").show(); // interactively query in-memory table
该foreach操作允许对输出数据计算任意操作。从Spark2.1开始,这仅适用于Scala和Java。要使用它,我们必须实现接口ForeachWriter,该接口具有在触发器之后生成作为输出生成的行序列时被调用的方法。请注意以下要点:
流式查询的触发器设置定义了流式数据处理的时间,查询是作为具有固定批处理间隔的微批量查询还是作为连续处理查询来执行。以下是支持的各种触发器:
触发类型 | 描述 |
未指定(默认) | 如果未明确指定触发设置,则默认情况下,查询将以微批处理模式执行,一旦上一批微批处理完成,就会生成微批次。 |
固定间隔微批次 | 查询将以微批处理模式执行,其中微批处理将以用户指定的间隔启动。
|
一次性微批次 | 查询将执行“进一个”微批处理所有可用数据,然后自行停止。这在我们希望定期启动集群,处理自上一个时间段以来可用的所有内容,然后关闭集群的方案中非常有用。在某些情况下,这可能会显著节省成本。 |
连续固定检查点间隔 | 查询将以新的低延迟,连续处理模式执行。 |
示例:
// Default trigger (runs micro-batch as soon as it can)
df.writeStream
.format("console")
.start();
// ProcessingTime trigger with two-seconds micro-batch interval
df.writeStream
.format("console")
.trigger(Trigger.ProcessingTime("2 seconds"))
.start();
// One-time trigger
df.writeStream
.format("console")
.trigger(Trigger.Once())
.start();
// Continuous trigger with one-second checkpointing interval
df.writeStream
.format("console")
.trigger(Trigger.Continuous("1 second"))
.start();
启动查询时创建的对象StreamingQuery可用于监视和管理查询:
StreamingQuery query = df.writeStream().format("console").start(); // get the query object
query.id(); // get the unique identifier of the running query that persists across restarts from checkpoint data
query.runId(); // get the unique id of this run of the query, which will be generated at every start/restart
query.name(); // get the name of the auto-generated or user-specified name
query.explain(); // print detailed explanations of the query
query.stop(); // stop the query
query.awaitTermination(); // block until query is terminated, with stop() or with error
query.exception(); // the exception if the query has been terminated with error
query.recentProgress(); // an array of the most recent progress updates for this query
query.lastProgress(); // the most recent progress update of this streaming query
我们可以在单个SparkSession中启动任意数量的查询。它们将同时运行,共享集群资源。我们可以使用sparkSession.streams()来获取StreamingQueryManager,用于管理当前查询操作。
SparkSession spark = ...
spark.streams().active(); // get the list of currently active streaming queries
spark.streams().get(id); // get a query object by its unique id
spark.streams().awaitAnyTermination(); // block until any one of them terminates
有多种方法可以监控流式查询操作。我们可以使用Spark的Dropwizard Metrics支持将指标推送到外部系统,也可以通过编程方式访问它们。
我们可以使用streamingQuery.lastProgress()和streamingQuery.status().lastProgress返回的一个StreamingQueryProgress对象来直接获取查询操作的当前状态和指标。对象中包含有关流的最后一次触发中所取得进展的所有信息:处理了哪些数据,处理速率,延迟等等。还有streamingQuery.recentProgress可以返回最后几个进展的数组。
此外,streamingQuery.status()返回一个StreamingQueryStatus对象,它提供了有关查询离级执行操作的信息:触发器是否处于操作状态,是否正在处理数据等。
示例:
StreamingQuery query = ...
System.out.println(query.lastProgress());
/* Will print something like the following.
{
"id" : "ce011fdc-8762-4dcb-84eb-a77333e28109",
"runId" : "88e2ff94-ede0-45a8-b687-6316fbef529a",
"name" : "MyQuery",
"timestamp" : "2016-12-14T18:45:24.873Z",
"numInputRows" : 10,
"inputRowsPerSecond" : 120.0,
"processedRowsPerSecond" : 200.0,
"durationMs" : {
"triggerExecution" : 3,
"getOffset" : 2
},
"eventTime" : {
"watermark" : "2016-12-14T18:45:24.873Z"
},
"stateOperators" : [ ],
"sources" : [ {
"description" : "KafkaSource[Subscribe[topic-0]]",
"startOffset" : {
"topic-0" : {
"2" : 0,
"4" : 1,
"1" : 1,
"3" : 1,
"0" : 1
}
},
"endOffset" : {
"topic-0" : {
"2" : 0,
"4" : 115,
"1" : 134,
"3" : 21,
"0" : 534
}
},
"numInputRows" : 10,
"inputRowsPerSecond" : 120.0,
"processedRowsPerSecond" : 200.0
} ],
"sink" : {
"description" : "MemorySink"
}
}
*/
System.out.println(query.status());
/* Will print something like the following.
{
"message" : "Waiting for data to arrive",
"isDataAvailable" : false,
"isTriggerActive" : false
}
*/
我们还可以通过给SparkSession附加一个StreamingQueryListener异步监控与之关联的所有查询。一旦使用sparkSession.stream.attachListener()方法附加我们自定义的StreamingQueryListener对象,我们就可以在启动和停止查询以及查询操作中获得回调参数,示例如下:
SparkSession spark = ...
spark.streams().addListener(new StreamingQueryListener() {
@Override
public void onQueryStarted(QueryStartedEvent queryStarted) {
System.out.println("Query started: " + queryStarted.id());
}
@Override
public void onQueryTerminated(QueryTerminatedEvent queryTerminated) {
System.out.println("Query terminated: " + queryTerminated.id());
}
@Override
public void onQueryProgress(QueryProgressEvent queryProgress) {
System.out.println("Query made progress: " + queryProgress.progress());
}
});
Spark支持使用Dropwizard库上报指标。要同时上报结构化流式查询的度量标准,我们必须在SparkSession中显示启用spark.sql.streaming.metricsEnabled配置。
spark.conf().set("spark.sql.streaming.metricsEnabled", "true");
// or
spark.sql("SET spark.sql.streaming.metricsEnabled=true");
启动此配置后,在SparkSession中启动的所有查询都会通过Dropwizard将指标报告给已配置的任何接收器(例如Ganglia,Graphite,JMX等)。
如果发生故障或者故意关机,我们可以恢复先前查询的进度和状态,并从中断除继续。这是使用checkpoint和预写日志完成的。我们可以使用checkpoint位置配置查询,查询将保存所有进度信息(即每个触发器中处理的偏移范围)和运行聚合(例如快速示例中的字数)到checkpoint位置。此checkpoint位置必须是HDFS兼容文件系统中的路径,并且可以在启动查询时设置未DataStreamWriter中的选项。
aggDF
.writeStream()
.outputMode("complete")
.option("checkpointLocation", "path/to/HDFS/dir")
.format("memory")
.start();