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<Row> 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<Row> 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();

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章