狀態(State)與一致性模型
接下來我們轉向另一個在流處理中十分重要的點:狀態(state)。狀態在數據處理中是無處不在的。為了產生一個結果,函數一般會聚合某個時間段內(或是一定數量的)events的狀態信息(例如計算聚合值,或是發現一個模式),有狀態的 operators使用流的輸入事件以及內部狀態,計算出它們的輸出。例如,一個滾動聚合operator輸出當前它讀入的所有的events的總和。Operator 持有當前sum的值作為它的內部狀態,並在每次讀入新值時對它做更新。類似,考慮一個operator在檢測到“高溫”后,若是在接下來的10分鍾內檢測到“煙”,則發出警報。此時operator需要將“高溫”事件存儲在它的狀態信息中,直到看見“煙”事件,或是到達10分鍾的過期時間。
由於流處理器可能會處理無限的數據,所以需要注意的是,不要讓內部state無限地增長下去。為了限制state 大小,一般來說,operators會維護一些到目前為止看到的所有events的概要(synopsis)、或是總結(summary)信息。這個摘要信息可以是 計數、sum、 一個樣本、亦或是用戶自定義的一個數據結構(用於存儲某些需要的屬性)。
對於支持有狀態的operators,會有以下挑戰:
1. 狀態管理
系統需要高效地管理state,並確保它可以被(合理地)並行更新
2. 狀態分區
在並行處理時,場景較為復雜,因為輸出的結果依賴於state以及持續輸入的events。不過在大部分場景中,我們可以通過key將state分區,並單獨的管理每個state。例如處理的輸入流為一組傳感器的events,這時可以使用分區的operator state 獨立地維護每個傳感器的狀態信息。
3. 狀態恢復
最大的挑戰為:在發生錯誤時,有狀態的operator需要確保state可以被恢復,並且仍能輸出正確的結果。
接下來我們詳細地討論一下任務失敗以及result guarantees。
任務失敗
在流處理工作中,opratror state是非常重要的信息,需要對此有容錯能力。如果在一次failure中,state丟失,則即使最終作業恢復了,輸出的結果可能仍是不對的。流處理工作一般會持續運行較長時間,所以state可能會在幾天(甚至幾月)才會被收集一次。如果重新去處理所有的輸入,以生成丟失的state,會是一個費時費算力的過程。
在實際場景中,可以經常見到有上百個並行任務(task)的流作業。而在長期運行的流作業中,每個任務在任何時間都有可能失敗。如何確保這些故障可以被透明地處理,並讓流工作繼續運行?事實上,我們需要流處理器不僅可以在任務失敗時仍能繼續處理,還需要保證結果以及operator state的正確性。接下來我們會對此討論具體細節。
什么是一個任務失敗
對於輸入流里的每一個event來說,一個任務是一個處理步驟,包含以下幾步:(1)接收到event,將它存儲在本地緩存;(2)可能會更新內部state;並(3)生成一個輸出條目。而故障可能在以上任意一步發生。如果故障發生在第一步,event是否會丟失?如果在已經更新了state后發生故障,state的信息在任務恢復后是否再次被更新?而在這些場景下,最終輸出的結果是否仍是准確的?
流系統通過提供結果保障(result guarantees)定義任務在故障發生時的行為。下面我們會介紹主流流處理器提供的保障(guarantees),以及為了達到這些guarantees,系統實現的一些機制。
至多一次(AT-MOST-ONCE)
當一個任務失敗時,最簡單的做法是不做任何事。至多一次確保每個event僅被處理一次 。也就是說,失敗了就失敗了。events可以簡單的被丟棄,並且不做任何事去確保結果的正確性。這種guarantee也被稱為“沒有保障“(no guarantee),因為一個丟棄所有events的系統也可以提供這種保障。沒有保障這點可能聽起來是一個比較差的想法,但是如果你關注的是盡量少的延遲,並且系統並不要求很高的准確性,則這也是一個可接受的選擇。
至少一次(AT-LEAST-ONCE)
在大部分真實應用中,我們會希望events不應該丟失。這種guarantee被稱為至少一次,意思是:所有的events均會被處理,並且有些可能會被處理不止一次。如果應用的准確性僅取決於所有事件的完整性,則重復的處理可能也是能被接受的。例如,如果場景是判斷某個特定的event是否在流中出現,則可以使用此guarantee。但是如果是判斷此特定event在流中出現的次數,則使用此gurantee會返回錯誤的結果。
為了確保at-least-once結果的准確性,我們需要有一種方式replay events(從數據源或是緩沖區)。持久化的 event log會將events寫入到可靠存儲,所以在task失敗時,這些events可以被replay。另一種達到同樣效果的方法是使用 記錄確認(record acknowledgments)。此方法會在緩存中存儲每個event,直到某個event被管道中所有的task 確認處理后,此event才會被丟棄。
精確一次(EXACTLY-ONCE)
精確一次是最嚴格的guarantee,並且很難達成。精確一次意思是:不僅沒有event丟失,而且在更新state時,每個event僅被應用一次。本質上說,exactly-once guarantee 表示應用會提供准確的結果,就像故障沒有發生一樣。
提供exactly-once guarantee需要at-least-once guarantees,所以數據replay的機制也是必須的。此外,流處理器還需要確保internal state的一致性。也就是說,在從錯誤恢復后,它應該能夠知道,一個event是否已經被state使用了。事務更新(transactional updates)是一種實現它的方式,但是它會引發大量性能開銷的問題。在Flink中,它用了一個輕量級的快照機制(snapshotting)達成exactly-once result guarantee。會在之后的章節進一步討論。
端到端精確一次(END-TO-END EXACTLY-ONCE)
到目前為止我們所見到的各種guarantees,都表示的是:由流處理器(stream processor)管理的application state。在真實的流應用中,除了流處理器外,都至少會有一個source和一個sink。End-to-end guarantees表示的是在整個數據處理pipeline中的結果准確性。每個組件提供了它自己的guarantee,所以整個pipeline的 end-to-end guarantee 是每個組件中最弱的那個guarantee。需要注意的是,有時候使用較弱的guarantees可能也能獲取更強guarantee的語義。一個常見的案例是:當一個task求最大值或最小值的時,使用at-least-once guarantess即可達到exactly-once語義的效果。
總結
到現在為止,我們獨立於Flink介紹了流處理中的一些概念,在之后的章節中,我們會介紹Flink是如何實現的這些概念,以及如何使用DataStream API 去編寫應用程序並使用介紹到的這些功能。
References:
Vasiliki Kalavri, Fabian Hueske. Stream Processing With Apache Flink. 2019