【Flink】(九)狀態一致性、端到端的精確一次(ecactly-once)保證

寫在前面:我是「雲祁」,一枚熱愛技術、會寫詩的大數據開發猿。暱稱來源於王安石詩中一句 [ 雲之祁祁,或雨於淵 ] ,甚是喜歡。


寫博客一方面是對自己學習的一點點總結及記錄,另一方面則是希望能夠幫助更多對大數據感興趣的朋友。如果你也對 數據中臺、數據建模、數據分析以及 Flink/Spark/Hadoop/數倉開發 感興趣,可以關注我 https://blog.csdn.net/BeiisBei ,讓我們一起挖掘數據的價值~


每天都要進步一點點,生命不是要超越別人,而是要超越自己! (ง •_•)ง

一、狀態一致性

當在分佈式系統中引入狀態時,自然也引入了一致性問題。一致性實際上是"正確性級別"的另一種說法,也就是說在成功處理故障並恢復之後得到的結果,與沒有發生任何故障時得到的結果相比,前者到底有多正確?舉例來說,假設要對最近一小時登錄的用戶計數。在系統經歷故障之後,計數結果是多少?如果有偏差,是有漏掉的計數還是重複計數?

在這裏插入圖片描述

  • 有狀態的流處理,內部每個算子任務都可以有自己的狀態
  • 對於流處理內部來說,所謂的狀態一致性,其實就是我們所說的計算結果保證準確。
  • 一條數據不應該丟失,也不應該重複計算
  • 在遇到故障時可以恢復狀態,恢復以後的重新計算,結果應該也是完全正確的。

1.1 一致性級別

在流處理中,一致性可以分爲 3 個級別:

  • at-most-once(最多一次): 這其實是沒有正確性保障的委婉說法——故障發生之後,計數結果可能丟失。同樣的還有 udp。
  • at-least-once (至少一次): 這表示計數結果可能大於正確值,但絕不會小於正確值。也就是說,計數程序在發生故障後可能多算,但是絕不會少算。
  • exactly-once (精確一次): 這指的是系統保證在發生故障後得到的計數結果與正確值一致。恰好處理一次是最嚴格的保證,也是最難實現的。

1.2 三個級別的區別

曾經,at-least-once 非常流行。第一代流處理器(如 Storm 和 Samza)剛問世時只保證 at-least-once,原因有二。

  • 保證 exactly-once 的系統實現起來更復雜。這在基礎架構層(決定什麼代表正確,以及 exactly-once 的範圍是什麼)和實現層都很有挑戰性。
  • 流處理系統的早期用戶願意接受框架的侷限性,並在應用層想辦法彌補(例如使應用程序具有冪等性,或者用批量計算層再做一遍計算)。

最先保證 exactly-once 的系統(Storm Trident 和 Spark Streaming)在性能和表現力這兩個方面付出了很大的代價。爲了保證 exactly-once,這些系統無法單獨地對每條記錄運用應用邏輯,而是同時處理多條(一批)記錄,保證對每一批的處理要麼全部成功,要麼全部失敗。這就導致在得到結果前,必須等待一批記錄處理結束。因此,用戶經常不得不使用兩個流處理框架(一個用來保證 exactly-once,另一個用來對每個元素做低延遲處理),結果使基礎設施更加複雜。曾經,用戶不得不在保證exactly-once 與獲得低延遲和效率之間權衡利弊。Flink 避免了這種權衡。

Flink 的一個重大價值在於,它既保證了 exactly-once,也具有低延遲和高吞吐的處理能力

從根本上說,Flink 通過使自身滿足所有需求來避免權衡,它是業界的一次意義重大的技術飛躍。儘管這在外行看來很神奇,但是一旦瞭解,就會恍然大悟。

二、一致性檢查點(Checkpoints)

  • Flink 使用了一種輕量級快照機制 ---- 檢查點(checkpoint)來保證 exactly-once 語義
  • 有狀態應用的一致檢查點,其實就是:所有任務的狀態,在某個時間點的一份拷貝(一份快照)。而這個時間點,應該是所有任務都恰好處理完一個相同的輸入數據的時候。
  • 應用狀態的一致檢查點,是 Flink 故障恢復機制的核心

在這裏插入圖片描述

三、端到端(end-to-end)狀態一致性

  • 目前我們看到的一致性保證都是由流處理器實現的,也就是說都是在 Flink 流處理器內部保證的;而在真實應用中,流處理應用除了流處理器以外還包含了數據源(例如 Kafka)和輸出到持久化系統。

  • 端到端的一致性保證,意味着結果的正確性貫穿了整個流處理應用的始終;每一個組件都保證了它自己的一致性

  • 整個端到端的一致性級別取決於所有組件中一致性最弱的組件。

四、端到端的精確一次(ecactly-once)保證

我們知道,端到端(end-to-end)狀態一致性取決於它所有組件中最薄弱的一環,也就是典型的木桶理論了。

具體可以劃分如下:

  • 內部保證 —— 依賴 checkpoint
  • source 端 —— 需要外部源可重設數據的讀取位置
  • sink 端 —— 需要保證從故障恢復時,數據不會重複寫入外部系統

而對於 sink 端,又有兩種具體的實現方式:冪等(Idempotent)寫入和事務性(Transactional)寫入。

4.1 冪等寫入(Idempotent Writes)

所謂冪等操作,是說一個操作,可以重複執行很多次,但只導致一次結果更改,也就是說,後面再重複執行就不起作用了。

Hashmap 的寫入插入操作是冪等的操作,重複寫入,寫入的結果還一樣。
在這裏插入圖片描述

4.2 事務寫入 (Transactional Writes)

需要構建事務來寫入外部系統,構建的事務對應着 checkpoint,等到 checkpoint 真正完成的時候,才把所有對應的結果寫入 sink 系統中。

事務 (Transaction)

  • 應用程序中一系列嚴密的操作,所有操作必須成功完成,否則在每個操作中所作的所有更改都會被撤銷
  • 具有原子性:一個事務中的一系列的操作要麼全部成功,要麼一個都不做

實現思想:構建的事務對應着 checkpoint,等到 checkpoint 真正完成的時候,才把所有對應的結果寫入 sink 系統中

對於事務性寫入,具體又有兩種實現方式:預寫日誌(WAL)兩階段提交(2PC)。DataStream API 提供了 GenericWriteAheadSink 模板類和TwoPhaseCommitSinkFunction 接口,可以方便地實現這兩種方式的事務性寫入。

4.2.1 預寫日誌(Write-Ahead-Log,WAL)

  • 把結果數據先當成狀態保存,然後在收到 checkpoint 完成的通知的時候,一次性寫入 sink 系統
  • 簡單易於實現,由於數據提前在狀態後端中做了緩存,所以無論什麼 sink 系統,都能用這種方式一批搞定
  • DataStream API 提供了一個模板類:GenericWriteAheadSink,來實現這種事務性 sink

4.2.2 兩階段提交(Two-Phase-Commit,2PC)

  • 對於每個 checkpoint,sink 任務會啓動一個事務,並將接下來所有接收的數據添加到事務裏
  • 然後將這些數據寫入外部的 sink 系統,但不提交它們 ----- 這時只是“預提交”
  • 當它收到 checkpoint 完成的通知時,它才正式提交事務,實現結果的真正寫入
  • 這種方式真正實現了 exactly-once ,它需要一個提供事務支持的外部 sink 系統。Flink 提供了 TwoPhaseCommitSinkFunction 接口。

4.2.3 2PC 對外部 sink 系統的要求

  • 外部 sink 系統必須提供事務支持,或者 sink 任務必須能夠模擬外部系統上的事務
  • 在 checkpoint 的間隔期間裏,必須能夠開啓一個事務並接受數據寫入
  • 在收到 checkpoint 完成的通知之前,事務必須是“等待提交” 的狀態。在故障恢復的情況下,這可能需要一些時間。如果這個時候 sink 系統關閉事務 (列如超時了),那麼未提交的數據就會丟失
  • sink 任務必須能夠在進程失敗後恢復事務
  • 提交事務必須是冪等操作

4.2.4 不同 Source 和 Sink 的一致性

在這裏插入圖片描述

五、Flink+Kafka 端到端狀態一致性的保證

我們知道,端到端的狀態一致性的實現,需要每一個組件都實現,對於 Flink + Kafka 的數據管道系統(Kafka 進、Kafka 出)而言,各組件怎樣保證 exactly-once語義呢?

  • 內部 —— 利用 checkpoint 機制,把狀態存盤,發生故障的時候可以恢復,
    保證內部的狀態一致性

  • source —— kafka consumer 作爲 source,可以將偏移量保存下來,如果後
    續任務出現了故障,恢復的時候可以由連接器重置偏移量,重新消費數據,
    保證一致性

  • sink —— kafka producer 作爲 sink,採用兩階段提交 sink,需要實現一個
    TwoPhaseCommitSinkFunction

內部的 checkpoint 機制我們已經有了瞭解,那 source 和 sink 具體又是怎樣運行的呢?接下來我們逐步做一個分析。

5.1 Exactly-once 兩階段提交

我們知道 Flink 由 JobManager 協調各個 TaskManager 進行 checkpoint 存儲,checkpoint 保存在 StateBackend 中,默認 StateBackend 是內存級的,也可以改爲文件級的進行持久化保存。

在這裏插入圖片描述

當 checkpoint 啓動時,JobManager 會將檢查點分界線(barrier)注入數據流;barrier 會在算子間傳遞下去。

在這裏插入圖片描述

每個算子會對當前的狀態做個快照,保存到狀態後端。對於 source 任務而言,就會把當前的 offset 作爲狀態保存起來。下次從 checkpoint 恢復時,source 任務可以重新提交偏移量,從上次保存的位置開始重新消費數據。

在這裏插入圖片描述
每個內部的 transform 任務遇到 barrier 時,都會把狀態存到 checkpoint 裏。sink 任務首先把數據寫入外部 kafka,這些數據都屬於預提交的事務(還不能被消費);當遇到 barrier 時,把狀態保存到狀態後端,並開啓新的預提交事務。

在這裏插入圖片描述
當所有算子任務的快照完成,也就是這次的 checkpoint 完成時,JobManager 會向所有任務發通知,確認這次 checkpoint 完成。
當 sink 任務收到確認通知,就會正式提交之前的事務,kafka 中未確認的數據就改爲“已確認”,數據就真正可以被消費了。

在這裏插入圖片描述
所以我們看到,執行過程實際上是一個兩段式提交,每個算子執行完成,會進行“預提交”,直到執行完 sink 操作,會發起“確認提交”,如果執行失敗,預提交會放棄掉。

5.2 兩階段提交步驟總結

具體的兩階段提交步驟總結如下:

  • 第一條數據來了之後,開啓一個 kafka 的事務(transaction),正常寫入
    kafka 分區日誌但標記爲未提交,這就是“預提交”
  • jobmanager 觸發 checkpoint 操作,barrier 從 source 開始向下傳遞,遇到
    barrier 的算子將狀態存入狀態後端,並通知 jobmanager
  • sink 連接器收到 barrier,保存當前狀態,存入 checkpoint,通知
    jobmanager,並開啓下一階段的事務,用於提交下個檢查點的數據
  • jobmanager 收到所有任務的通知,發出確認信息,表示 checkpoint 完成
  • sink 任務收到 jobmanager 的確認信息,正式提交這段時間的數據
  • 外部 kafka 關閉事務,提交的數據可以正常消費了。

所以我們也可以看到,如果宕機需要通過 StateBackend 進行恢復,只能恢復所有確認提交的操作,關於後端狀態的選擇可以看【Flink】(七)狀態管理

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