Flink 原理與實現:Checkpoint

衆所周知,Flink 採用 Asynchronous Barrier Snapshotting(簡稱 ABS)算法實現分佈式快照的。但是,本文着重介紹 Flink Checkpoint 工作過程,並且用圖形化方式描述 Checkpoint 在 Flink 中的實現,Failure Recovery Mechanism(失敗恢復機制),以及 Performance of Checkpointing。

1. Consistent Checkpoint

1.1 Naive Checkpoint

有狀態的流式應用程序的 Consistent Checkpoint 是指,在某一時刻每個算子在所有的算子已經處理完相同的輸入時的 State 副本(建議先閱讀Flink 原理與實現:State)。Naive Consistent Checkpoint 可以描述成以下步驟:

  1. 暫停所有輸入流的接收;
  2. 等待正在處理的數據完成計算,換言之,所有算子必須已經處理完它們所有的輸入數據;
  3. 拷貝每個算子的 State 到遠程,並持久化存儲之。當所有算子都已經完成了各自的拷貝,那麼這次的 Checkpoint 也就完成了;
  4. 繼續接收輸入流。

但是,Flink 並沒有直接採用 Naive Consistent Checkpoint,我將在後文介紹 Flink 採用的更精緻的 Checkpoint 算法。圖1 展示了 Naive Consistent Checkpoint 的過程。
圖1 流式應用程序的 Naive Consistent Checkpoint
上圖中有一個輸入流持續生產自增的正整數,即 1、2、3……Source 將這些數字按照奇偶分到兩個分區中,並將輸入流的當前 Offset 作爲 State 存儲起來。然後,由 Sum 算子對接收到的數字進行求和運算。在圖1 中,做 Checkpoint 時,Source 中的 Offset 是 5,奇偶 Sum 算子中的和分別是 9、6。

1.2 Recovery from Consistent Checkpoint

在流式應用程序執行的過程中,Flink 會週期性地 Checkpoint 應用程序的 State。爲了從失敗中恢復,Flink 會從最近 Checkpoint 的 State 中重新啓動處理數據。這種 Recovery Mechanism 可以分爲 3 個步驟:

  1. 重啓整個應用程序;
  2. 把所有狀態算子的 State 重置爲最近一次的 Checkpoint State;
  3. 繼續所有算子的處理。

這種 Failure Recovery Mechanism 可以用圖2 簡單的表示。
圖2 Recovering from a checkpoint
通過對所有算子做 Checkpoint,存儲所有算子的 State,以及所有輸入流可以把被消費的位置重置到 Checkpoint 的 State 的地方,這種 Checkpoint 和 Failure Recovery Mechanism 方法可以保證 Extractly-Once 語義。由此觀之,一個流式應用程序能否支持 Extractly-Once ,取決於輸入流是否滿足以下特性:

  1. 輸入流提供 Offset;
  2. 輸入流可以重放,以便重置到之前的 Offset。

比如,訂閱 Apache Kafka 消息的流式應用程序可以支持 Extractly-Once,相反,消費 Socket 的流式應用程序卻做不到。需要注意的一點是,當流式應用程序從 Checkpoint 的 State 重啓之後,它也會處理該 Checkpoint 之後和本次失敗之前的數據。換句話說,系統會對一些數據處理兩次,即便如此,這種機制仍然可以保證 State 一致性,或者說結果的正確性,因爲所有算子的 State 會被重置至它們還沒處理到這些數據的位置(關於 Extractly-Once 語義的真正含義,建議閱讀流計算中的 Exactly Once 語義)。

另一個值得一提的事情是,Flink 的 Checkpoint 和 Failure Recovery Mechanism 僅僅只能保證內部 State 的 Extractly-Once 。對於有 Sink 算子的應用程序,在 Recovery 的過程中,有些數據會被多次 Sink 到外部存儲系統,比如文件系統,數據庫系統等。對於 Flink 的 End-to-End Extractly-Once(端到端的 Extractly-Once),我後續會有文章專門討論(請關注筆者 (♥◠‿◠♥))。

2. Flink Checkpoint Algorithm

前面討論過 Naive Consistent Checkpoint 需要流式應用程序暫停、Checkpoint、重啓,這就引入了 “Stop-the-World(STW)”。由於 STW,這對於中等延遲要求的應用程序而言都不實際。雖然 Flink 的 Checkpoint 也是基於 Consistent Checkpoint,不過幸運的是,Flink 實現 Checkpoint 是採用了 Chandy-Lamport 算法的改進版 Asynchronous Barrier Snapshotting 算法。該算法把 Checkpoint 從算子處理中解耦,在不停止整個應用程序的情況下,某些算子可以繼續處理數據,而其他算子則持久化它們的 State。下文就開始介紹這個算法的工作過程了。

Flink Checkpoint 算法使用了一個特殊的數據結構——Checkpoint Barrier。Checkpoint Barrier 被 Source 算子注入到數據流中,但是不會影響數據的正常處理。Checkpoint Barrier 用 Checkpoint ID 標誌它所屬的 Checkpoint,並在邏輯上將流分爲兩部分。Checkpoint Barrier 之前的數據的所有 State 修改,包含在這個 Barrier 對應的 Checkpoint 中;這個 Checkpoint Barrier 之後的數據的所有 State 修改,包含在後續的 Checkpoint 中。例如,在圖3 中 Checkpoint Barrier N 前面紅色的數據和 Checkpoint Barrier N 的顏色相同,表示這些數據都是由 Checkpoint Barrier N 應該負責,而 Checkpoint Barrier N 後面黃色的數據就不屬於 Checkpoint Barrier N。
圖3 Checkpoint Barrier
明白 Checkpoint Barrier 之後,現在我用下面這個例子一步一步解釋這個過程:假設有兩個 Source 算子分別消費兩個生產自增正整數的輸入流;然後,Source 的結果分區到奇偶兩個 Sum 算子,Sum 算子會對接收到的正整數進行求和運算;最後,算子的結果會下發到 Sink 算子。這個過程可以簡單的表示成圖4。
圖4 2 個有狀態的 Source,2 個有狀態的算子,2 個無狀態的 Sink
JobManager 發送一條帶有 Checkpoint ID 的 Barrier 到每個 Source 算子,進行 Checkpoint 的初始化,如圖5 所示。
圖5 JobManager 初始化 Checkpoint,併發往所有的 Source
當 Source 算子接收到 Barrier 時,它就暫停提交數據,同時 Checkpoint 本地 State 到 State Backend,並且廣播攜帶 Checkpoint ID 的這個 Checkpoint Barrier 到所有下游分區。一旦算子 的 State Checkpoint 完成,並且算子確認在 JobManager 中有這個 Checkpoint 的信息,State Backend 就會通知該算子。當所有的 Barrier 發出後,Source 算子繼續照常執行。通過注入 Barrier,Source 算子知道 Checkpoint 發生在流中的哪個位置。圖6 展示這一過程。
圖6 Source checkpoint 各自的 state,並且提交 Checkpoint Barrier
Checkpoint Barrier 廣播到所有連接的並行算子,以確保每個算子可以從輸入分區中接收 Barrier。當一個算子接收到一次新的 Checkpoint 對應的 Barrier 時,它會等待所有輸入分區中屬於這次 Checkpoint 的 Barrier 到達。然而在等待的過程中,它會繼續處理來自尚未提供 Barrier 的流中的數據。在算子的其中一個輸入分區的 Barrier 之後到達,且屬於該 Barrier 的已到達數據不能被處理並被緩存,以等待其他輸入分區的 Barrier 到達。等待所有 Barrier 的過程叫作 Barrier Alignment,它可以用圖7 簡單的表示出來。
圖7 Task 等待接收每個輸入分區的 Barrier;緩存輸入流中已到達的 Barrier 對應的數據;正常處理其他所有數據
如果算子接收到它的輸入分區中的 Barrier,它將在 State Backend 中初始化一個 Checkpoint,並且廣播該 Checkpoint 的 Barrier 到它的所有下游,如圖7 所示。
圖8 一旦所有的 Barrier 都到達時,Task checkpoint 它們各自的狀態,並且將 Checkpoint Barrier 繼續向下遊發送
一旦所有的 Barrier 都已提交,算子就開始處理緩存的數據。當緩存中的所有數據也提交之後,算子繼續處理輸入流。圖9 表示了這個過程。
圖9 在 Checkpoint Barrier 下發之後,算子繼續照常處理數據
當 Sink 算子接收到一個 Barrier 的時候,它也會像其他算子那樣——進行 Barrier Alignment,Checkpoint 它自己的 State,確認 JobManager 已收到該 Barrier。當 JobManager 收到應用程序的所有算子的 Checkpoint 的確認,就標誌着這個應用程序的 Checkpoint 過程已完成。圖10 描述了這最後一步。完成的 Checkpoint 也可像前文描述的那樣用於失敗恢復。
圖10 Sink 收到 Checkpoint Barrier 覆信告知 JobManager,並且當所有的 Task 都已確認各自的 State checkpoint 成功,則該 Checkpoint 完成
爲了簡單起見,還可以採用填表的方法描述 Flink Checkpoint 過程。還用上面的例子,當黃藍色這個 Job 的 所有算子在表中填滿之後,表示本次 Checkpoint 完成,如圖11 所示。
圖11 Checkpoint 填表法

3. Performance of Checkpointing

Flink Checkpoint 算法可以避免 STW,但是,爲了進一步提高流式應用程序的處理延遲,Flink 做了以下這些調整,可以在某些情況下提升性能:

  1. Extractly-Once with Incremental Checkpointing。
  2. At-Least-Once

在 Extractly-Once 的場景中,任務 Checkpoint 它的 State 時,需要緩存數據以便 Barrier Alignment。由於 State 會變得比較大,Checkpoint 通過網絡寫數據到遠程存儲時,可能會花費數秒鐘到數分鐘不等。在 Flink 的設計中,State Backend 負責存儲 Checkpoint。所以,具體怎樣拷貝任務的 State 取決於 State Backend 的實現。比如,文件系統和 RocksDB 作爲 Stat Backend 時,都支持異步 Checkpoint。當 Checkpoint 觸發時,State Backend 會創建本地的 State 拷貝。本地拷貝創建完之後,任務繼續照常運行。後臺線程會異步將本地拷貝快照到遠程存儲,並且當 Checkpoint 完成時,通知任務。異步 Checkpoint 大大減少了任務繼續處理數據前的時間。另外,RocksDB State Backend 的 Incremental Checkpointing 特性,可以降低數據傳輸時間。

另一個降低 Checkpoint 延遲的方法通過對 Barrier Alignment 這個環節的調整。對於某些實時性要求高的流式應用程序,Flink 可以配置成在 Barrier Alignment 期間處理所有到達的數據,而不是緩存它們。一旦一個 Checkpoint 的所有 Barrier 都到達,該算子就 Checkpoint 它的 State,這也包括屬於下一個 Checkpoint 的數據。這種情況下,當失敗恢復時,這些數據會被再次處理,此時 Flink 提供的就是 At-Least-Once 保證。

4. 總結

本文介紹了 Naive Consistent Checkpoint、Flink Checkpoint Algorithm、Failure Recovery Mechanism,和如何調整 Flink Checkpoint 的性能。

掃碼關注公衆號:冰山烈焰的黑板報
在這裏插入圖片描述

發佈了101 篇原創文章 · 獲贊 122 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章