全面講解Flink中CheckPoint機制和Exactly Once / At Least Once應用

看完本文,你能 get 到以下知識:

  1. 介紹 CheckPoint 如何保障 Flink 任務的高可用
  2. CheckPoint 中的狀態簡介
  3. 如何實現全域一致的分佈式快照?
  4. 什麼是 barrier?什麼是 barrier 對齊?
  5. 證明了:爲什麼 barrier 對齊就是 Exactly Once,爲什麼 barrier 不對齊就是 At Least Once。

Flink 簡介
有狀態函數和運算符在各個元素/事件的處理中存儲數據(狀態數據可以修改和查詢,可以自己維護,根據自己的業務場景,保存歷史數據或者中間結果到狀態中)

例如:

• 當應用程序搜索某些事件模式時,狀態將存儲到目前爲止遇到的事件序列。
• 在每分鐘/小時/天聚合事件時,狀態保存待處理的聚合。
• 當在數據點流上訓練機器學習模型時,狀態保持模型參數的當前版本。
• 當需要管理歷史數據時,狀態允許有效訪問過去發生的事件。

什麼是狀態?
無狀態計算的例子:

• 比如:我們只是進行一個字符串拼接,輸入 a,輸出 a_666,輸入b,輸出 b_666輸出的結果跟之前的狀態沒關係,符合冪等性。
• 冪等性:就是用戶對於同一操作發起的一次請求或者多次請求的結果是一致的,不會因爲多次點擊而產生了副作用
有狀態計算的例子:
• 計算 pv、uv。
• 輸出的結果跟之前的狀態有關係,不符合冪等性,訪問多次,pv 會增加。

Flink 的 CheckPoint 功能簡介

1.Flink CheckPoint 的存在就是爲了解決 Flink 任務 failover 掉之後,能夠正常恢復任務。那 CheckPoint 具體做了哪些功能,爲什麼任務掛掉之後,通過 CheckPoint 能使得任務恢復呢?
2.CheckPoint 是通過給程序快照的方式使得將歷史某些時刻的狀態保存下來,當任務掛掉之後,默認從最近一次保存的完整快照處進行恢復任務。問題來了,快照是什麼鬼?能吃嗎?
3.SnapShot 翻譯爲快照,指將程序中某些信息存一份,後期可以用來恢復。對於一個 Flink 任務來講,快照裏面到底保存着什麼信息呢?
4.晦澀難懂的概念怎麼辦?當然用案例來代替咯,用案例讓大家理解快照裏面到底存什麼信息。選一個大家都比較清楚的指標,app 的 pv,Flink 該怎麼統計呢?

我們從 Kafka 讀取到一條條的日誌,從日誌中解析出 app_id,然後將統計的結果放到內存中一個 Map 集合,app_id 作爲 key,對應的 pv 做爲 value,每次只需要將相應 app_id 的 pv 值 +1 後 put 到 Map 中即可。
在這裏插入圖片描述
5.Flink 的 Source task 記錄了當前消費到 kafka test topic 的所有 partition 的 offset,爲了方便理解 CheckPoint 的作用,這裏先用一個 partition 進行講解,假設名爲 “test”的 topic 只有一個 partition0。

例:(0,1000)

表示 0 號 partition 目前消費到 offset 爲 1000 的數據

6.Flink 的 pv task 記錄了當前計算的各 app 的 pv 值,爲了方便講解,我這裏有兩個 app:app1、app2

• 例:(app1,50000)(app2,10000)
o 表示 app1 當前 pv 值爲 50000
o 表示 app2 當前 pv 值爲 10000
• 每來一條數據,只需要確定相應 app_id,將相應的 value 值 +1 後 put 到 map 中即可。

7.該案例中,CheckPoint 到底記錄了什麼信息呢?

• offset:(0,1000)
• pv:(app1,50000)(app2,10000)
• 記錄的其實就是第 n 次 CheckPoint 消費的 offset 信息和各 app 的 pv 值信息,記錄一下發生 CheckPoint 當前的狀態信息,並將該狀態信息保存到相應的狀態後端。(注:狀態後端是保存狀態的地方,決定狀態如何保存,如何保障狀態高可用,我們只需要知道,我們能從狀態後端拿到 offset 信息和 pv 信息即可。狀態後端必須是高可用的,否則我們的狀態後端經常出現故障,會導致無法通過 checkpoint 來恢復我們的應用程序)
• chk-100
• 該狀態信息表示第 100 次 CheckPoint 的時候, partition 0 offset 消費到了 1000,pv 統計結果爲(app1,50000)(app2,10000)。

8.任務掛了,如何恢復?

• 假如我們設置了三分鐘進行一次 CheckPoint,保存了上述所說的 chk-100 的 CheckPoint 狀態後,過了十秒鐘,offset 已經消費到(0,1100),pv 統計結果變成了(app1,50080)(app2,10020),但是突然任務掛了,怎麼辦?
• 莫慌,其實很簡單,flink只需要從最近一次成功的 CheckPoint 保存的offset(0,1000)處接着消費即可,當然pv值也要按照狀態裏的 pv 值(app1,50000)(app2,10000)進行累加,不能從(app1,50080)(app2,10020)處進行累加,因爲 partition 0 offset 消費到 1000 時,pv 統計結果爲(app1,50000)(app2,10000)。
當然如果你想從 offset (0,1100)pv(app1,50080)(app2,10020)這個狀態恢復,也是做不到的,因爲那個時刻程序突然掛了,這個狀態根本沒有保存下來。我們能做的最高效方式就是從最近一次成功的 CheckPoint 處恢復,也就是我一直所說的 chk-100。
• 以上講解,基本就是 CheckPoint 承擔的工作,描述的場景比較簡單。

9.疑問,計算 pv 的 task 在一直運行,它怎麼知道什麼時候去做這個快照?或者說計算 pv 的 task 怎麼保障它自己計算的 pv 值(app1,50000)(app2,10000)就是 offset(0,1000)那一刻的統計結果呢?

在這裏插入圖片描述

• barrier 從 Source Task 處生成,一直流到 Sink Task,期間所有的 Task 只要碰到barrier,就會觸發自身進行快照。
o CheckPoint barrier n-1 處做的快照就是指 Job 從開始處理到 barrier n-1所有的狀態數據。
o barrier n 處做的快照就是指從 Job 開始到處理到 barrier n 所有的狀態數據。

• 對應到 pv 案例中就是,SourceTask 接收到 JobManager 的編號爲 chk-100 的 CheckPoint 觸發請求後,發現自己恰好接收到 kafka offset(0,1000)處的數據,所以會往 offset(0,1000)數據之後 offset(0,1001)數據之前安插一個 barrier,然後自己開始做快照,也就是將 offset(0,1000)保存到狀態後端 chk-100 中。然後 barrier 接着往下游發送,當統計 pv 的 task 接收到 barrier 後,也會暫停處理數據,將自己內存中保存的 pv 信息(app1,50000)。(app2,10000)保存到狀態後端 chk-100 中。OK,Flink 大概就是通過這個原理來保存快照的。
o 統計 pv 的 task 接收到 barrier,就意味着 barrier 之前的數據都處理了,所以說,不會出現丟數據的情況。

• barrier 的作用就是爲了把數據區分開,CheckPoint 過程中有一個同步做快照的環節不能處理 barrier 之後的數據,爲什麼呢?
o 如果做快照的同時,也在處理數據,那麼處理的數據可能會修改快照內容,所以先暫停處理數據,把內存中快照保存好後,再處理數據。
o 結合案例來講就是,統計 pv 的 task 想對(app1,50000)(app2,10000)做快照,但是如果數據還在處理,可能快照還沒保存下來,狀態已經變成了(app1,50001)(app2,10001),快照就不準確了,就不能保障 Exactly Once 了。

• Flink 是在數據中加了一個叫做 barrier 的東西(barrier 中文翻譯:柵欄),上圖中紅圈處就是兩個 barrier。

10.總結

• 流式計算中狀態交互

在這裏插入圖片描述

11.簡易場景精確一次的容錯方法

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

• 消費到 Y 位置的時候,將 Y 對應的狀態保存下來
• 消費到 X 位置的時候,將 X 對應的狀態保存下來
• 週期性地對消費 offset 和統計的狀態信息或統計結果進行快照

多並行度、多 Operator 情況下,CheckPoint 過程
1.分佈式狀態容錯面臨的問題與挑戰
• 如何確保狀態擁有精確一次的容錯保證?
• 如何在分佈式場景下替多個擁有本地狀態的算子產生一個全域一致的快照?
• 如何在不中斷運算的前提下產生快照?

2.多並行度、多 Operator 實例的情況下,如何做全域一致的快照?
所有的 Operator 運行過程中遇到 barrier 後,都對自身的狀態進行一次快照,保存到相應狀態後端

在這裏插入圖片描述

對應到 pv 案例:有的 Operator 計算的 app1 的 pv,有的 Operator 計算的 app2的 pv,當他們碰到 barrier 時,都需要將目前統計的 pv 信息快照到狀態後端。

3.多 Operator 狀態恢復
在這裏插入圖片描述

4.具體怎麼做這個快照呢?
利用之前所有的 barrier 策略。
在這裏插入圖片描述

JobManager 向 SourceTask 發送 CheckPointTrigger,SourceTask 會在數據流中安插 CheckPoint barrier。

在這裏插入圖片描述

Source Task 自身做快照,並保存到狀態後端。

在這裏插入圖片描述

Source Task 將 barrier 跟數據流一塊往下游發送。
在這裏插入圖片描述

當下遊的 Operator 實例接收到 CheckPointbarrier 後,對自身做快照。
在這裏插入圖片描述

在這裏插入圖片描述

上述圖中,有 4 個帶狀態的 Operator 實例,相應的狀態後端就可以想象成填 4 個格子。整個 CheckPoint 的過程可以當做 Operator 實例填自己格子的過程,Operator 實例將自身的狀態寫到狀態後端中相應的格子,當所有的格子填滿可以簡單的認爲一次完整的 CheckPoint 做完了。

5.上面只是快照的過程,整個 CheckPoint 執行過程如下

• JobManager 端的 CheckPointCoordinator 向所有 SourceTask 發送 CheckPointTrigger,Source Task 會在數據流中安插 CheckPoint barrier。
• 當 task 收到所有的 barrier 後,向自己的下游繼續傳遞 barrier,然後自身執行快照,並將自己的狀態異步寫入到持久化存儲中。
o 增量 CheckPoint 只是把最新的一部分更新寫入到 外部存儲;
o 爲了下游儘快做 CheckPoint,所以會先發送 barrier 到下游,自身再同步進行快照;
• 當 task 完成備份後,會將備份數據的地址(state handle)通知給 JobManager 的 CheckPointCoordinator。
o 如果 CheckPoint 的持續時長超過了 CheckPoint 設定的超時時間,CheckPointCoordinator 還沒有收集完所有的 State Handle,CheckPointCoordinator 就會認爲本次 CheckPoint 失敗,會把這次 CheckPoint 產生的所有狀態數據全部刪除。
• 最後 CheckPointCoordinator 會把整個 StateHandle 封裝成 completed CheckPoint Meta,寫入到 hdfs。

6.barrier 對齊
什麼是 barrier 對齊?
在這裏插入圖片描述

  1. 一旦 Operator 從輸入流接收到 CheckPointbarrier n,它就不能處理來自該流的任何數據記錄,直到它從其他所有輸入接收到 barrier n 爲止。否則,它會混合屬於快照 n 的記錄和屬於快照 n + 1 的記錄。
  2. 接收到 barrier n 的流暫時被擱置。從這些流接收的記錄不會被處理,而是放入輸入緩衝區。
    上圖中第 2 個圖,雖然數字流對應的 barrier 已經到達了,但是 barrier 之後的 1、2、3 這些數據只能放到 buffer 中,等待字母流的 barrier 到達。
  3. 一旦最後所有輸入流都接收到 barrier n,Operator 就會把緩衝區中 pending 的輸出數據發出去,然後把 CheckPoint barrier n 接着往下游發送。
    這裏還會對自身進行快照。
  4. 之後,Operator 將繼續處理來自所有輸入流的記錄,在處理來自流的記錄之前先處理來自輸入緩衝區的記錄。

什麼是 barrier 不對齊?

  1. 上述圖 2 中,當還有其他輸入流的 barrier 還沒有到達時,會把已到達的 barrier 之後的數據 1、2、3 擱置在緩衝區,等待其他流的 barrier 到達後才能處理。
  2. barrier 不對齊就是指當還有其他流的 barrier 還沒到達時,爲了不影響性能,也不用理會,直接處理 barrier 之後的數據。等到所有流的 barrier 的都到達後,就可以對該 Operator 做 CheckPoint 了

爲什麼要進行 barrier 對齊?不對齊到底行不行?

  1. Exactly Once 時必須 barrier 對齊,如果 barrier 不對齊就變成了 At Least Once。後面的部分主要證明這句話。
  2. CheckPoint 的目的就是爲了保存快照,如果不對齊,那麼在 chk-100 快照之前,已經處理了一些 chk-100 對應的 offset 之後的數據,當程序從 chk-100 恢復任務時,chk-100 對應的 offset 之後的數據還會被處理一次,所以就出現了重複消費。如果聽不懂沒關係,後面有案例讓您懂。
    • 結合 pv 案例來看,之前的案例爲了簡單,描述的 kafka 的 topic 只有 1 個 partition,這裏爲了講述 barrier 對齊,所以 topic 有 2 個 partittion。

在這裏插入圖片描述

  1. Flink 同樣會起四個 Operator 實例,我還稱他們是 TaskA0、TaskA1、TaskB0、TaskB1。四個 Operator 會從狀態後端讀取保存的狀態信息。
  2. 從 offset:(0,10000)(1,10005) 開始消費,並且基於 pv:(app0,8000)(app1,12050)值進行累加統計。
  3. 然後你就應該會發現這個 app1 的 pv 值 12050 實際上已經包含了 partition1 的 offset 10005~10200 的數據,所以 partition1 從 offset 10005 恢復任務時,partition1 的 offset 10005~10200 的數據被消費了兩次。
  4. TaskB1 設置的 barrier 不對齊,所以 CheckPoint chk-100 對應的狀態中多消費了 barrier 之後的一些數據(TaskA1 發送),重啓後是從 chk-100 保存的 offset 恢復,這就是所說的 At Least Once。
  5. 由於上面說 TaskB0 設置的 barrier 對齊,所以 app0 不會出現重複消費,因爲 app0 沒有消費 offset:(0,10000)(1,10005) 之後的數據,也就是所謂的 Exactly Once。
    • chk-100
    • offset:(0,10000)(1,10005)
    • pv:(app0,8000) (app1,12050)
  6. 雖然狀態保存的 pv 值偏高了,但是不能說明重複處理,因爲我的 TaskA1 並沒有再次去消費 partition1 的 offset 10005~10200 的數據,所以相當於也沒有重複消費,只是展示的結果更實時了。
  7. 這裏假如 TaskA0 消費的 partition0 的 offset 爲 10000,TaskA1 消費的 partition1 的 offset 爲 10005。那麼狀態中會保存 (0,10000)(1,10005),表示 0 號 partition 消費到了 offset 爲 10000 的位置,1 號 partition 消費到了 offset 爲 10005 的位置。
  8. 結合業務,先介紹一下上述所有算子在業務中的功能:
    • Source 的 kafka 的 Consumer,從 kakfa 中讀取數據到 Flink 應用中
    • TaskA 中的 map 將讀取到的一條 kafka 日誌轉換爲我們需要統計的 app_id
    • keyBy 按照 app_id 進行 keyBy,相同的 app_id 會分到下游 TaskB的同一個實例中
    • TaskB 的 map 在狀態中查出該 app_id 對應的 pv 值,然後 +1,存儲到狀態中
    • 利用 Sink 將統計的 pv 值寫入到外部存儲介質中
  9. 我們從 kafka 的兩個 partition 消費數據,TaskA 和 TaskB 都有兩個並行度,所以總共 Flink 有 4 個 Operator 實例,這裏我們稱之爲 TaskA0、TaskA1、TaskB0、TaskB1。
  10. 假設已經成功做了 99 次 CheckPoint,這裏詳細解釋第 100 次 CheckPoint 過程。
    • JobManager 內部有個定時調度,假如現在 10 點 00 分 00 秒到了第 100 次 CheckPoint 的時間了,JobManager 的 CheckPointCoordinator 進程會向所有的 Source Task 發送 CheckPointTrigger,也就是向 TaskA0、TaskA1 發送 CheckPointTrigger。
    • TaskA0、TaskA1 接收到 CheckPointTrigger,會往數據流中安插 barrier,將 barrier 發送到下游,在自己的狀態中記錄 barrier 安插的 offset 位置,然後自身做快照,將 offset 信息保存到狀態後端。
    然後 TaskA 的 map 和 keyBy 算子中並沒有狀態,所以不需要進行快照。
    • 接着數據和 barrier 都向下游 TaskB 發送,相同的 app_id 會發送到相同的TaskB實例上,這裏假設有兩個 app:app0 和 app1,經過 keyBy 後,假設 app0 分到了 TaskB0 上,app1 分到了 TaskB1 上。基於上面描述,TaskA0 和 TaskA1 中的所有 app0 的數據都發送到 TaskB0 上,所有 app1 的數據都發送到 TaskB1 上。
    • 現在我們假設 TaskB0 做 CheckPoint 的時候 barrier 對齊了,TaskB1 做 CheckPoint 的時候 barrier 不對齊,當然不能這麼配置,我就是舉這麼個例子,帶大家分析一下 barrier 對不對齊到底對統計結果有什麼影響?
    • 上面說了 chk-100 的這次 CheckPoint,offset 位置爲(0,10000)(1,10005),TaskB0 使用 barrier 對齊,也就是說 TaskB0 不會處理 barrier 之後的數據,所以TaskB0 在 chk-100 快照的時候,狀態後端保存的 app0 的 pv 數據是從程序開始啓動到 kafkaoffset 位置爲(0,10000)(1,10005)的所有數據計算出來的 pv 值,一條不多(沒處理 barrier 之後,所以不會重複),一條不少(barrier 之前的所有數據都處理了,所以不會丟失),假如保存的狀態信息爲(app0,8000)表示消費到(0,10000)(1,10005)offset 的時候,app0 的 pv 值爲 8000。
    • TaskB1 使用的 barrier 不對齊,假如 TaskA0 由於服務器的 CPU 或者網絡等其他波動,導致 TaskA0 處理數據較慢,而 TaskA1 很穩定,所以處理數據比較快。導致的結果就是 TaskB1 先接收到了 TaskA1 的 barrier,由於配置的 barrier 不對齊,所以 TaskB1 會接着處理 TaskA1 barrier 之後的數據,過了 2 秒後,TaskB1 接收到了 TaskA0 的 barrier,於是對狀態中存儲的 app1 的 pv 值開始做 CheckPoint 快照,保存的狀態信息爲(app1,12050),但是我們知道這個(app1,12050)實際上多處理了 2 秒 TaskA1 發來的 barrier 之後的數據,也就是 kafka topic 對應的 partition1 offset 10005 之後的數據,app1 真實的 pv 數據肯定要小於這個 12050,partition1 的 offset 保存的 offset 雖然是 10005,但是我們實際上可能已經處理到了 offset 10200 的數據,假設就是處理到了 10200。
  11. 分析到這裏,我們先梳理一下我們的狀態保存了什麼:
    • chk-100
    • offset:(0,10000)(1,10005)
    • pv:(app0,8000) (app1,12050)
  12. 接着程序在繼續運行,過了 10 秒,由於某個服務器掛了,導致我們的四個 Operator 實例有一個 Operator 掛了,所以 Flink 會從最近一次的狀態恢復,也就是我們剛剛詳細講的 chk-100 處恢復,那具體是怎麼恢復的呢?
    o Flink 同樣會起四個 Operator 實例,我還稱他們是 TaskA0、TaskA1、TaskB0、TaskB1。四個 Operator 會從狀態後端讀取保存的狀態信息。
    o 從 offset:(0,10000)(1,10005) 開始消費,並且基於 pv:(app0,8000) (app1,12050)值進行累加統計
    o 然後你就應該會發現這個 app1 的 pv 值 12050 實際上已經包含了 partition1 的 offset 10005~10200 的數據,所以 partition1 從 offset 10005 恢復任務時,partition1 的 offset 10005~10200 的數據被消費了兩次。
    o TaskB1 設置的 barrier 不對齊,所以 CheckPoint chk-100 對應的狀態中多消費了 barrier 之後的一些數據(TaskA1 發送),重啓後是從 chk-100 保存的 offset 恢復,這就是所說的 At Least Once。
    o 由於上面說 TaskB0 設置的 barrier 對齊,所以 app0 不會出現重複消費,因爲 app0 沒有消費 offset:(0,10000)(1,10005) 之後的數據,也就是所謂的 Exactly Once。
    看到這裏你應該已經知道了哪種情況會出現重複消費了,也應該要掌握爲什麼 barrier 對齊就是 Exactly Once,爲什麼 barrier 不對齊就是 At Least Once。
    這裏再補充一個問題,到底什麼時候會出現 barrier 對齊?
    • 首先設置了 Flink 的 CheckPoint 語義是:Exactly Once。
    • Operator 實例必須有多個輸入流纔會出現 barrier 對齊。
    o 對齊,漢語詞彙,釋義爲使兩個以上事物配合或接觸得整齊。由漢語解釋可得對齊肯定需要兩個以上事物,所以,必須有多個流才叫對齊。barrier 對齊其實也就是上游多個流配合使得數據對齊的過程。
    o 言外之意:如果 Operator 實例只有一個輸入流,就根本不存在 barrier 對齊,自己跟自己默認永遠都是對齊的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章