Spark每日半小时(33)——结构化流式编程:流式查询的启动、管理、监控以及Checkpointing

启动流式查询

一旦定义了最终结果DataFrame/Dataset,剩下的的就是开始流式计算。为此,我们必须使用Dataset.writeStream()方法返回的的DataStreamWriter。我们必须在此界面中指定以下一项或多项参数。

  • 输出接收器的详细信心:数据格式,位置等。
  • 输出模式:指定写入输出接收器的内容。
  • 查询名称:可选,指定查询的唯一名称以进行标识。
  • 触发间隔:可选,如果未指定,则系统将在前一处理完成后立即检查新数据的可用性。如果由于先前处理尚未完成而错过了触发事件,则系统将离级触发处理。
  • 检查点位置:对于可以保证端到端容错的某些输出接收器,请指定系统写入所有检查点信息的位置。这应该时与HDFS兼容大的容错文件系统中的目录。

输出模式

有几种类型的输出模式:

  • 追加模式(默认):这是默认模式,其中只有自上次触发后添加到结果表的新行才会输出到接收器。仅支持那些添加到结果表中的行永远不会更改的查询。因此,此模式保证每行仅输出一次(加收容错接收器)。例如,仅查询select,where,map,flatMap,join,等会支持追加模式。
  • 完成模式:每次触发后,整个结果表会将输出到接收器。聚合查询支持此功能。
  • 更新模式:仅将结果表中自上次触发后更新的行输出到接收器。在将来的版本中添加更多信息。

不同类型的流式查询支持不同的输出模式。这是兼容性矩阵:

查询类型   支持的输出模式 笔记
具有聚合的查询 事件时间与水印的聚合 追加,更新,完成 追加模式使用水印来删除旧的聚合状态。但是窗口聚合的输出延迟了'withWatermark()'中指定的后期阈值,如模式语义,在结束后(即超过水印后)行只能添加到结果表中一次。
其他聚合 完成,更新

由于未定义水印(仅在其他类别中定义),因此不会丢弃旧的聚合状态。

不支持追加模式,因为聚合可以更新,因此违反了此模式的语义。

查询mapGroupsWithState   更新  
查询flatMapGroupsWithState 追加操作模式 追加 之后允许聚合flatMapGroupsWithState。
更新操作模式 更新 之后不允许聚合flatMapGroupsWithState。
查询joins   追加 尚不支持更新和完成模式。
其他查询   追加,更新 不支持完成模式,因为在结果表中保留所有未聚合数据是不可行的。

输出接收器

有集中类型的内置输出接收器:

  • 文件接收器:将输出存储到目录。
writeStream
    .format("parquet")        // can be "orc", "json", "csv", etc.
    .option("path", "path/to/destination/dir")
    .start()
  • Kafka sink:将输出存储到Kafka中的一个或多个主题。
writeStream
    .format("kafka")
    .option("kafka.bootstrap.servers", "host1:port1,host2:port2")
    .option("topic", "updates")
    .start()
  • Foreach接收器:对输出中的记录运行任意计算。
writeStream
    .foreach(...)
    .start()
  • 控制台接收器(用于调试):每次触发时将输出打印到控制台/标准输出。支持Append和Complete输出模式。这应该用于低数据量的调试,因为在每次触发后收集整个输出并将其存储在驱动成勋的内存中。
writeStream
    .format("console")
    .start()
  • 内存接收器(用于调试):输出作为内存表存储在内存中。支持Append和Complete输出模式。这应该用于低数据量的调试,因为输出被手机并存储咋i驱动程序的内存中。因此,请谨慎使用。
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

该foreach操作允许对输出数据计算任意操作。从Spark2.1开始,这仅适用于Scala和Java。要使用它,我们必须实现接口ForeachWriter,该接口具有在触发器之后生成作为输出生成的行序列时被调用的方法。请注意以下要点:

  • 编写器必须是可序列化的,因为它将被序列化并发送给执行程序以供执行。
  • 所有这三种方法,open,process以及close将在执行者调用。
  • 只有在调用open方法时,编写者才必须进行所有初始化(例如,打开连接,启动事务等)。请注意,如果在创建对象中有任何初始化,那么初始化将在驱动程序中发生(因为这是创建实例的地方),这可能不是我们想要的。
  • version和partition是open方法唯一两个参数,version是一个递增id,随着每个触发器而增加。partition是一个表示输出分区的id,因为输出是分布式的,并且将在多个执行程序上处理。
  • open可以使用version和partition选择是否需要编写行序列。因此,它可以返回true(继续写入),或false(不需要写入)。如果返回false,则process不会再任何行上调用。例如,再部分失败后,失败触发器的某些输出分区可能已经提交到数据库。基于存储在数据库中的元数据,编写器可以识别已经提交的分区,并相应的返回false以跳过在此提交它们。
  • 每当open被调用时,close也会被调用(除非JVM因某些错误而推出)。即使open返回false也是如此。如果在处理起和写入数据时出现任何错误,close将调用失败。我们有责任清理已创建的状态(例如连接,事务等),以避免open方法资源泄漏。

触发器

流式查询的触发器设置定义了流式数据处理的时间,查询是作为具有固定批处理间隔的微批量查询还是作为连续处理查询来执行。以下是支持的各种触发器:

触发类型 描述
未指定(默认) 如果未明确指定触发设置,则默认情况下,查询将以微批处理模式执行,一旦上一批微批处理完成,就会生成微批次。
固定间隔微批次

查询将以微批处理模式执行,其中微批处理将以用户指定的间隔启动。

  • 如果先前的微批次在该间隔内完成,则引擎将等待该间隔结束,然后开始下一个微批次。
  • 如果前一个微批次需要的时间长于完成的间隔(即如果错过了间隔边界),则下一个微批次将在前一个完成后立即开始(即,它不会等待下一个间隔边界) )。
  • 如果没有可用的新数据,则不会启动微批次。
一次性微批次 查询将执行“进一个”微批处理所有可用数据,然后自行停止。这在我们希望定期启动集群,处理自上一个时间段以来可用的所有内容,然后关闭集群的方案中非常有用。在某些情况下,这可能会显著节省成本。
连续固定检查点间隔 查询将以新的低延迟,连续处理模式执行。

示例:

// 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
}
*/

使用异步API以编程方式报告度量标准

我们还可以通过给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());
    }
});

使用Dropwizard报告度量标准

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位置。此checkpoint位置必须是HDFS兼容文件系统中的路径,并且可以在启动查询时设置未DataStreamWriter中的选项。

aggDF
  .writeStream()
  .outputMode("complete")
  .option("checkpointLocation", "path/to/HDFS/dir")
  .format("memory")
  .start();

 

你可能感兴趣的:(#,Spark每日半小时)