Spark Structured Streaming: 自維護(任意)狀態流的“超時”(Timeout)問題

此“超時”非彼“超時”

在我們開始這篇文章之前,我們必須要先弄清除一下問題:爲什麼流的上的狀態會有“超時”問題?超時機制是爲什麼樣的業務場景而設計的?通常情形下,人們一種直白的想法是:某種狀態在長時間沒有得到來自新數據的更新時,我們可以認爲這個狀態是“超時”了,它應該不復存在了,應該永遠的被移除掉。然而遺憾的時是,Spark對於“狀態”以及“超時”是另外一種理解:

Spark認爲既然流是沒有邊界的,那麼以某個key產生的支流(就是mapGroupsWithState或者flatMapGroupsWithState維護的有狀態的流)上的“狀態”(一個Key對應的GroupState實例)也將是“不眠不休”的,即永不會消亡,而在GroupState上對State對象的移除動作並不是對當前“狀態”的一種終結操作,新的State對象會在新數據的驅動下被重新創建出來,同樣的,對於GroupState上的“超時”,它也不是像我們前面預想的那樣:是代表着一種由於流的“終結”而觸發的“絕響”(超時過後,這條支流及其狀態將不復存在),而也只是永不消亡的GroupState實例上的某個中間狀態!同樣的,超時處理完後,還是會有新的數據進來,新的State對象還是會在新數據的驅動下被重新創建出來。

所以,如果你的業務邏輯是存在一系列的“事件”,這些“事件”前後關聯,形成一種session,OK,你自然會想到以session的ID爲Key爲每個session構建一個自維護的State來維持這個session, 但如果這個session會終結的,那麼你這樣做就會出問題。首先,如前所述,這個構建出來的狀態是永遠不會消亡的,它會一直存在而不會被資源回收,更重要的是,如果你是寄希望通過超時來觸發session的終結操作,則這將可能永遠不會被觸發,因爲在Spark上,所有自維護(任意)狀態的流,超時都是在有新的數據進入時檢測時間差來判定的,當你的session發出最後一個“事件”後,不會再新的事件發生讓spark來觸發超時檢測了!

基於處理時間和事件和事件時間的超時處理的異同

對於自維護的狀態,超時既可以基於處理時間(i.e. GroupStateTimeout.ProcessingTimeTimeout) 又可以基於事件時間 (i.e. GroupStateTimeout.EventTimeTimeout),如果選擇了基於”處理時間“,則超時的duration是通過GroupState.setTimeoutDuration來設定的,但是超時並非是嚴格地發生在設定的duration的時間點上,而是在當剛剛超過duration的那次trigger發生時,所以說timeout並沒有一個嚴格的“上限”(也就是最晚發生的時間,相對應的“下限“是指最早的發生時間,這個時間是剛好在duration的時間點上),舉個例子:如果流上在最近一段時間沒有任何數據,則整個負責維護狀態的函數根本不會被觸發,也就不會執行任何超時檢測,直到有新的數據到達,這樣有可能會導致在很長時間之後纔會觸發超時處理,而並不是在規定的那個超時時間點上觸發的,這就是所謂的timeout並沒有嚴格的“上限”的意思。

如果選擇的是基於“事件時間”,首先要開啓基於事件時間框架,即必須在查詢中使用Dataset.withWatermark(),然後需要在GroupState中使用GroupState.setTimeoutTimestamp()設定超時,setTimeoutTimestamp有兩類版本,一類是設定一個固定時間戳,另一類是在一個指定的時間戳上再指定一個duration, 前者適用於那些有明確超時時間點的場景,後者適用於那些在某個最新的事件時間上再追加一個duration的場景。但是有一點是非常重要和清楚的,就是這個每次設定的超時時間戳是不能晚於watermark的!因爲Spark在基於”事件時間“ 判定超時的原則就是:當且僅當watermark超過(晚於)了設定的timeout!某種意義上,我們可以認爲watermark是當前“狀態實例”(不是GroupState,而是它包裹的那個State對象)認定”存活“的一個”開始“的時間界限,timeout是當前”狀態實例“認定”存活“的一個”截止“的時間界限,如果開始的時間比截止的時間還要晚,說明這個狀態實例超時了!最後,在基於”事件時間“時,對於實際的超時時間也是沒有一個準確的”上限“的,這和基於“事件時間”的超時判定的原因是一樣的,因爲watermark也是在流中有新數據時纔會被觸發更新,進而計算超時的時間。

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