Intro
Flink之所以能夠做到高效而準確的有狀態流式處理,核心是依賴于檢查點(checkpoint)機制。當流式程序運行出現異常時,能夠從最近的一個檢查點恢復,從而最大限度地保證數據不丟失也不重復。
Flink檢查點本質上是通過異步屏障快照(asychronous barrier snapshot, ABS)算法產生的全局狀態快照,一般是存儲在分布式文件系統(如HDFS)上。但是,如果狀態空間超大(比如key非常多或者窗口區間很長),檢查點數據可能會達到GB甚至TB級別,每次做checkpoint都會非常耗時。但是,海量狀態數據在檢查點之間的變化往往沒有那么明顯,增量檢查點可以很好地解決這個問題。顧名思義,增量檢查點只包含本次checkpoint與上次checkpoint狀態之間的差異,而不是所有狀態,變得更加輕量級了。
Incremental CP on RocksDB Backend
目前Flink有3種狀態后端,即內存(MemoryStateBackend)、文件系統(FsStateBackend)和RocksDB(RocksDBStateBackend),只有RocksDB狀態后端支持增量檢查點。該功能默認關閉,要打開它可以在flink-conf.yaml中配置:
state.backend: rocksdb
state.backend.incremental: true
或者在代碼中配置:
RocksDBStateBackend rocksDBStateBackend = new RocksDBStateBackend("hdfs://path/to/flink-checkpoints", true);
env.setStateBackend(rocksDBStateBackend);
為什么只有RocksDB狀態后端支持增量檢查點呢?這是由RocksDB本身的特性決定的。RocksDB是一個基于日志結構合并樹(LSM樹)的鍵值式存儲引擎,它可以視為HBase等引擎的思想基礎,故與HBase肯定有諸多相似之處。如果看官不了解LSM樹的話,可以通過筆者之前寫的這篇文章來做個簡單的了解。
在RocksDB中,扮演讀寫緩存的角色叫做memtable。memtable寫滿之后會flush到磁盤形成數據文件,叫做sstable(是“有序序列表”即sorted sequence table的縮寫)。RocksDB也存在compaction策略,在后臺合并已經寫入的sstable,原有的sstable會包含所有的鍵值對,合并前的sstable在此后會被刪除。關于compaction,筆者寫了一篇非常詳細的文章來探討,見這里。
在啟用RocksDB狀態后端后,Flink的每個checkpoint周期都會記錄RocksDB庫的快照,并持久化到文件系統中。所以RocksDB的預寫日志(WAL)機制可以安全地關閉,沒有重放數據的必要性了。
Illustrating Incremental CP
有了上面的鋪墊,下面通過例子來解釋增量檢查點的過程。
上圖示出一個有狀態的算子的4個檢查點,其ID為2,并且state.checkpoints.num-retained
參數設為2,表示保留2個檢查點。表格中的4列分別表示RocksDB中的sstable文件,sstable文件與存儲系統中文件路徑的映射,sstable文件的引用計數,以及保留的檢查點的范圍。
下面按部就班地解釋一下:
- 檢查點CP 1完成后,產生了兩個sstable文件,即sstable-(1)與sstable-(2)。這兩個文件會寫到持久化存儲(如HDFS),并將它們的引用計數記為1。
- 檢查點CP 2完成后,新增了兩個sstable文件,即sstable-(3)與sstable-(4),這兩個文件的引用計數記為1。并且由于我們要保留2個檢查點,所以上一步CP 1產生的兩個文件也要算在CP 2內,故sstable-(1)與sstable-(2)的引用計數會加1,變成2。
- 檢查點CP 3完成后,RocksDB的compaction線程將sstable-(1)、sstable-(2)、sstable-(3)三個文件合并成了一個文件sstable-(1,2,3)。CP 2產生的sstable-(4)得以保留,引用計數變為2,并且又產生了新的sstable-(5)文件。注意此時CP 1已經過期,所以sstable-(1)、sstable-(2)兩個文件不會再被引用,引用計數減1。
- 檢查點CP 4完成后,RocksDB的compaction線程將sstable-(4)、sstable-(5)以及新生成的sstable-(6)三個文件合并成了sstable-(4,5,6),并對sstable-(1,2,3)、sstable-(4,5,6)引用加1。由于CP 2也過期了,所以sstable-([1~4])四個文件的引用計數同時減1,這就造成sstable-(1)、sstable-(2)、sstable-(3)的引用計數變為0,Flink就從存儲系統中刪除掉這三個文件。
通過上面的分析,我們可以看出Flink增量檢查點機制的巧妙之處:
- 通過跟蹤sstable的新增和刪除,可以記錄狀態數據的變化;
- 通過引用計數的方式,上一個檢查點中已經存在的文件可以直接被引用,不被引用的文件可以及時刪除;
- 可以保證當前有效的檢查點都不引用已經刪除的文件,從而保留
state.checkpoints.num-retained
參數指定的狀態歷史。
What to Concern...
增量檢查點解決了大狀態checkpointing的問題,但是在從檢查點恢復現場時會帶來潛在的overhead。這是顯然的:當程序出問題后,TaskManager需要從多個檢查點中加載狀態數據,并且這些數據中還可能會包含將被刪除的狀態。
還有一點,就算磁盤空間緊張,舊檢查點的文件也不能隨便刪除,因為新檢查點仍然會引用它們,如果貿然刪除,程序就無法恢復現場了。這就提示我們,如果狀態本身的數據量不大,并且狀態之間的overlap也不明顯的話,開啟增量檢查點可能會造成反效果(checkpoint數據量異常膨脹),所以應該按需使用。
The End
明天還要繼續搬磚,民那晚安。