Spark基於事件時間的“狀態”流的深層分析 - withWatermark與mapGroupsWithState的關係

不管是基於watermark的窗口計算還是自維護的狀態流,它們都是有狀態的,watermark只是規定了數據進入“狀態”(有資格參與狀態計算)的條件,並沒有(也不適合)聲明狀態的“退出”機制。對於watermark的窗口計算來說,它們的“退出”機制是:如果最近某個還處於active狀態的窗口它的EndTime比當前批次中最新的一個事件時間減去watermark規定的閾值還要“早”,說明這個窗口所有的數據都就緒了,不會再被更新了,就可以把正式“decommission”了。由於這個邏輯對於watermark的窗口計算來說是通行的, 所以被Spark封裝在窗口計算中,對開發人員是透明的,但是對於自維護的狀態來說“退出”機制是要根據實際情況進行處理的,因此必須要由開發員人員通過編碼來實現,這其中除了業務邏輯上決定的“主動”退出(例如接收到了某類session關閉的消息)之外,還需要有一種“保底”的推出機制:狀態超時,對於某些有狀態的流,可能並沒有對應的關閉消息,可以約定在多長時間內沒有收到消息就認定狀態終結了,那這時就是基於時間閾值的判斷,那就又會涉及到是基於事件時間還是處理時間,顯然,Spark是同時支持兩種模式的,只是有一點會讓新人疑惑的是:在基於事件時間的有狀態的流計算上,在API層面“似乎”沒有給開發人員一個聲明“哪個字段是事件時間”的地方,轉而是這樣約定的:如果開發人員需要開發基於事件時間的有狀態的流計算,則必須使用watermark機制,對應到代碼層面就是,當你使用mapGroupsWithState(GroupStateTimeout.EventTimeTimeout())(yourGroupStateUpdateFunc)時,前面一定要先聲明withWatermark("yourEnenTimeColumnName", yourWatermarkDuration)

sparkSession
    ...
    .withWatermark("yourEnenTimeColumnName", yourWatermarkDuration)
    ...
    .groupByKey(...)
    .mapGroupsWithState(GroupStateTimeout.EventTimeTimeout())(yourGroupStateUpdateFunc)
    ...

我們怎麼解讀這兩者之間的關係呢?首先:我們應該清晰地認識到:這裏涉及到的是兩個“時間”點和三種“度量”標準的問題,兩個“時間”點指的是:數據“進入”狀態參與計算的“時效條件”與狀態退出的“時效條件”,三種”度量“標準是指以事件時間爲準還是以處理時間爲準還是不指定時間維度上度量閾值,以下是對它們之間邏輯上的關聯關係的總結:

基於事件時間 基於處理時間 不指定
由Spark維護的有狀態計算(如window計算),決定數據是否能進入狀態的“時效條件” withWatermark(…) N/A,不適用,因爲當事件沒有進入狀態前,是沒有”處理時間”這個值的,也就不存在這種case 如果不指定,即沒有聲明withWatermark,直接進行窗口計算,此時的含義將變爲“不設條件”,所有事件“無條件”進入窗口進行計算。
由Spark維護的有狀態計算(如window計算),決定狀態是否退出的“時效條件” 首先前提是必須聲明瞭withWatermark(…),這是確保基於事件時間的前提。如果該窗口的EndTime比當前批次中最新的一個事件時間減去watermark規定的閾值還要“早”,則自動退出,相關中間狀態和退出動作由Spark封裝,不需要顯式的編碼工作 N/A,不適用,對於狀態的退出,雖然可以參照處理時間,但是Spark並沒有提供這方面的支持,顯然,Spark認爲基於事件時間是更有實際意義的 如果不指定,即沒有申明withWatermark,則所有的窗口對應的狀態永不“退出”,不管過去多長時間都可以被更新(實際編碼時是不可取的)
自維護的狀態,決定數據是否能進入狀態的“時效條件” withWatermark(…) N/A,不適用,因爲當事件沒有進入狀態前,是沒有”處理時間”這個值的,也就不存在這種case 如果不指定,即沒有聲明withWatermark,直接進行窗口計算,此時的含義將變爲“不設條件”,所有事件“無條件”進入窗口進行計算。
自維護的狀態,決定狀態是否退出的“時效條件” 首先前提是必須聲明瞭withWatermark(…),這是確保基於事件時間的前提。然後使用mapGroupsWithState (GroupStateTimeout.EventTimeTimeout())(…),顯式地表明狀態的超時將以事件時間爲準,超時閾值在每個GroupState中使用setTimeoutTimestamp單獨設定! 不聲明withWatermark,表示不使用事件時間框架,然後使用 mapGroupsWithState (GroupStateTimeout.ProcessingTimeTimeout())(…),顯式地表明狀態的超時將以處理時間爲準,超時閾值在每個GroupState中使用setTimeoutDuration單獨設定! 即不設置不在GroupState中設置任何超時,這時狀態永遠不會退出!(不推薦)

從是否使用withWatermark的角度總結是:

  1. 一旦使用withWatermark(…), 就意味着:在決定數據是否能進入狀態時,完全基於withWatermark指定的事件時間去計算,而在決定狀態是否退出時,對於基於窗口的由Spark來維護的狀態計算,會基於withWatermark指定的閾值基於事件時間進行判斷,對於自維護的狀態,可以基於事件時間,也可以基於處理時間。
  2. 但是如果沒有使用withWatermark(…),就意味着:在決定數據是否能進入狀態時,是無條件的,所有數據都會進入狀態,而在決定狀態是否退出時,對於基於窗口的由Spark維護的狀態計算,狀態永不退出,對於自維護的狀態,可以永不退出,如果基於時間進行退出,只能基於“處理時間”,因爲沒有使用withWatermark(…)就意味着沒有開啓時間時間框架,數據進入狀態與狀態退出都不能依賴事件時間。

從“狀態”是由Spark維護還是自維護的角度總結是:

  1. 如果“狀態”是由Spark維護的基於窗口的流,如果使用withWatermark(…),則數據進入狀態與狀態退出都是基於事件時間的。如果沒有使用withWatermark(…),不存在基於哪種時間決定數據進入狀態與狀態退出,而是無條件進入與永不退出!
  2. 如果是“狀態”自維護的計算,如果使用withWatermark(…),則數據是否進入狀態是基於事件時間判斷的,狀態退出可以基於事件時間或處理時間判斷,取決於設置的是GroupStateTimeout.EventTimeTimeout還是GroupStateTimeout.ProcessTimeTimeout

以上總結比較晦澀難懂,究其原因,我認爲是Spark Structured Streaming的API沒有在一開始很好的規劃這些事情,而是在早期沒有考慮完善的版本上逐漸累加新API導致的,這可以從Spark的版本歷史中看出來,相信Spark以後會梳理出一些更加完備而直白的API。

本文原文鏈接: http://blog.csdn.net/bluishglc/article/details/80824522 轉載請註明出處。

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