Flink 中的容錯機制

在 Flink 中,有一套完整的容錯機制來保證故障後的恢復,其中最重要的就是檢查點。

1.檢查點(Checkpoint)

在流處理中,我們可以用存檔讀檔的思路,就是將之前某個時間點所有的狀態保存下來,這份“存檔”就是所謂的“檢查點”(checkpoint)。

遇到故障重啓的時候,我們可以從檢查點中“讀檔”,恢復出之前的狀態,這樣就可以回到當時保存的一刻接着處理數據了。

這裏所謂的“檢查”,其實是針對故障恢復的結果而言的:故障恢復之後繼續處理的結果,應該與發生故障前完全一致,我們需要“檢查”結果的正確性。所以,有時又會把checkpoint叫做“一致性檢查點”。

1.檢查點的保存

1.週期性的觸發保存

“隨時存檔”確實恢復起來方便,可是需要我們不停地做存檔操作。如果每處理一條數據就進行檢查點的保存,當大量數據同時到來時,就會耗費很多資源來頻繁做檢查點,數據處理的速度就會受到影響。所以在 Flink中,檢查點的保存是週期性觸發的,間隔時間可以進行設置。

2.保存的時間點

我們應該在所有任務(算子)都恰好處理完一個相同的輸入數據的時候,將它們的狀態保存下來。

這樣做可以實現一個數據被所有任務(算子)完整地處理完,狀態得到了保存。

如果出現故障,我們恢復到之前保存的狀態,故障時正在處理的所有數據都需要重新處理;我們只需要讓源(source)任務向數據源重新提交偏移量、請求重放數據就可以了。當然這需要源任務可以把偏移量作爲算子狀態保存下來,而且外部數據源能夠重置偏移量;kafka 就是滿足這些要求的一個最好的例子。

3.保存的具體流程

檢查點的保存,最關鍵的就是要等所有任務將“同一個數據”處理完畢。下面我們通過一個具體的例子,來詳細描述一下檢查點具體的保存過程。

回憶一下我們最初實現的統計詞頻的程序——word count。這裏爲了方便,我們直接從數據源讀入已經分開的一個個單詞,例如這裏輸入的是:“hello”,“world”,“hello”,“flink”,“hello”,“world”,“hello”,“flink”…

我們所需要的就是每個任務都處理完“hello”之後保存自己的狀態。

2.從檢查點恢復狀態

當我們需要保存檢查點時,就是在所有任務處理完同一條數據後,對狀態做個快照保存下來。例如我們輸入數據爲:“hello”,“world”,“hello”,“flink”,“hello”,“world”,“hello”,“flink”…

我們所需要的就是每個任務都處理完“hello”之後保存自己的狀態。

3.檢查點算法

在 Flink 中,採用了基於 Chandy-Lamport 算法的分佈式快照,可以在不暫停整體流處理的前提下,將狀態備份保存到檢查點。

1.檢查點分界線(Barrier)

借鑑水位線的設計,在數據流中插入一個特殊的數據結構,專門用來表示觸發檢查點保存的時間點。收到保存檢查點的指令後,Source 任務可以在當前數據流中插入這個結構;之後的所有任務只要遇到它就開始對狀態做持久化快照保存。由於數據流是保持順序依次處理的,因此遇到這個標識就代表之前的數據都處理完了,可以保存一個檢查點;而在它之後的數據,引起的狀態改變就不會體現在這個檢查點中,而需要保存到下一個檢查點。

這種特殊的數據形式,把一條流上的數據按照不同的檢查點分隔開,所以就叫做檢查點的“分界線”(Checkpoint Barrier)。

在JobManager中有一個“檢查點協調器”,專門用來協調處理檢查點的相關工作。檢查點協調器會定期向TaskManager發出指令,要求保存檢查點(帶着檢查點ID);TaskManager會讓所有的Source任務把自己的偏移量(算子狀態)保存起來,並將帶有檢查點ID的分界線插入到當前的數據流中,然後像正常的數據一樣像下游傳遞;之後Source任務就可以繼續讀入新的數據了。

2.分佈式快照算法(Barrier 對齊的精準一次)

watermark 指示的是“之前的數據全部到齊了”,而 barrier 指示的是“之前所有數據的狀態更改保存入當前檢查點”:它們都是一個“截止時間”的標誌。所以在處理多個分區的傳遞時,也要以是否還會有數據到來作爲一個判斷標準。

具體實現上,Flink使用了 Chandy-Lamport算法的一種變體,被稱爲“異步分界線快照”算法。算法的核心就是兩個原則

當上遊任務向多個並行下游任務發送 barrier 時,需要廣播出去;

而當多個上游任務向同一個下游任務傳遞分界線時,需要在下游任務執行“分界線對齊”操作,也就是需要等到所有並行分區的 barrier 都到齊,纔可以開始狀態的保存。

1.場景說明

爲了詳細解釋檢查點算法的原理,我們對之前的word count程序進行擴展,考慮所有算子並行度爲2的場景。

我們有兩個並行的Source任務,會分別讀取兩個數據流(或者是一個源的不同分區)。這裏每條流中的數據都是一個個的單詞:“hello”“world”“hello” “flink”交 替出 現。此時第一條流的Source任務(爲了方便,下文中我們直接叫它“Source 1”,其它任務類似)讀取了3個數據,偏移量爲3;而第二條流的Source任務(Source 2)只讀取了一個“hello”數據,偏移量爲1。

2.檢查點保存算法具體過程爲

1)JobManager發送指令,觸發檢查點的保存;Source 任務中插入一個分界線,並將偏移量保存到遠程的持久化存儲中。

說明:並行的Source任務保存的狀態爲3和1,表示當前的1號檢查點應該包含:第一條流中截至第三個數據、第二條流中截至第一個數據的所有狀態更改。可以發現Source任務做這些的時候並不影響後面任務的處理,Sum任務已經處理完了第一條流中傳來的(world, 1),對應的狀態也有了更改。

  1. 觸發檢查點:JobManager 向 Source 發送 Barrier;
  2. Barrier 發送:向下遊廣播發送;
  3. Barrier 對齊:下游需要收到上游所有並行度傳遞過來的 Barrier 才做自身狀態的保存;
  4. 狀態保存:有狀態的算子將狀態保存至持久化。
  5. 先處理緩存數據,然後正常繼續處理

完成檢查點保存之後,任務就可以繼續正常處理數據了。這時如果有等待分界線對齊時緩存的數據,需要先做處理;然後再按照順序依次處理新到的數據。當 JobManager 收到所有任務成功保存狀態的信息,就可以確認當前檢查點成功保存。之後遇到故障就可以從這裏恢復了。

(補充)由於分界線對齊要求先到達的分區做緩存等待,一定程度上會影響處理的速度;當出現背壓時,下游任務會堆積大量的緩衝數據,檢查點可能需要很久纔可以保存完畢。

爲了應對這種場景,Barrier 對齊中提供了至少一次語義以及 Flink 1.11 之後提供了不對齊的檢查點保存方式,可以將未處理的緩衝數據也保存進檢查點。這樣,當我們遇到一個分區barrier 時就不需等待對齊,而是可以直接啓動狀態的保存了。

3.分佈式快照算法(Barrier 對齊的至少一次)

(1)JobManager發送指令,觸發檢查點的保存;Source 任務中插入一個分界線,並將偏移量保存到遠程的持久化存儲中。

說明:並行的Source任務保存的狀態爲3和1,表示當前的1號檢查點應該包含:第一條流中截至第三個數據、第二條流中截至第一個數據的所有狀態更改。可以發現Source任務做這些的時候並不影響後面任務的處理,Sum任務已經處理完了第一條流中傳來的(world, 1),對應的狀態也有了更改。

4.分佈式快照算法(非 Barrier 對齊的精準一次)

(1)JobManager發送指令,觸發檢查點的保存;Source 任務中插入一個分界線,並將偏移量保存到遠程的持久化存儲中。

說明:並行的Source任務保存的狀態爲3和1,表示當前的1號檢查點應該包含:第一條流中截至第三個數據、第二條流中截至第一個數據的所有狀態更改。可以發現Source任務做這些的時候並不影響後面任務的處理,Sum 2已經處理完了第一條流中傳來(world, 1)對應的狀態也有了更改。

4.檢查點配置

檢查點的作用是爲了故障恢復,我們不能因爲保存檢查點佔據了大量時間、導致數據處理性能明顯降低。爲了兼顧容錯性和處理性能,我們可以在代碼中對檢查點進行各種配置。

1.啓用檢查點

默認情況下,Flink 程序是禁用檢查點的。如果想要爲 Flink 應用開啓自動保存快照的功能,需要在代碼中顯式地調用執行環境的.enableCheckpointing()方法:

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // 每隔 1 秒啓動一次檢查點保存
        env.enableCheckpointing(1000);

這裏需要傳入一個長整型的毫秒數,表示週期性保存檢查點的間隔時間。如果不傳參數直接啓用檢查點,默認的間隔週期爲 500 毫秒,這種方式已經被棄用。

檢查點的間隔時間是對處理性能和故障恢復速度的一個權衡。如果我們希望對性能的影響更小,可以調大間隔時間;而如果希望故障重啓後迅速趕上實時的數據處理,就需要將間隔時間設小一些。

2.檢查點存儲

檢查點具體的持久化存儲位置,取決於“檢查點存儲”的設置。默認情況下,檢查點存儲在 JobManager的堆內存中。而對於大狀態的持久化保存,Flink也提供了在其他存儲位置進行保存的接口。

具體可以通過調用檢查點配置的.setCheckpointStorage()來配置,需要傳入一個 CheckpointStorage 的實現類。Flink 主要提供了兩種CheckpointStorage:作業管理器的堆內存和文件系統。

        // 配置存儲檢查點到 JobManager 堆內存
        env.getCheckpointConfig().setCheckpointStorage(new JobManagerCheckpointStorage());
        // 配置存儲檢查點到文件系統
        env.getCheckpointConfig().setCheckpointStorage(new FileSystemCheckpointStorage("hdfs://namenode:40010/flink/checkpoints"));

對於實際生產應用,我們一般會將 CheckpointStorage 配置爲高可用的分佈式文件系統(HDFS,S3 等)。

3.其它高級配置

檢查點還有很多可以配置的選項,可以通過獲取檢查點配置(CheckpointConfig)來進行設置。

CheckpointConfig checkpointConfig = env.getCheckpointConfig();
1.常用高級配置
  • 檢查點模式(CheckpointingMode)
    設置檢查點一致性的保證級別,有“精確一次”(exactly-once)和“至少一次”(at-least-once)兩個選項。默認級別爲 exactly-once,而對於大多數低延遲的流處理程序,at-least-once就夠用了,而且處理效率會更高。

  • 超時時間(checkpointTimeout)
    用於指定檢查點保存的超時時間,超時沒完成就會被丟棄掉。傳入一個長整型毫秒數作爲參數,表示超時時間。

  • 最小間隔時間(minPauseBetweenCheckpoints)
    用於指定在上一個檢查點完成之後,檢查點協調器最快等多久可以出發保存下一個檢查點的指令。這就意味着即使已經達到了週期觸發的時間點,只要距離上一個檢查點完成的間隔不夠,就依然不能開啓下一次檢查點的保存。這就爲正常處理數據留下了充足的間隙。當指定這個參數時,實際併發爲 1。

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