Flink 必知必會經典課程四:Fault-tolerance in Flink

分享人:本文由 Apache Flink PMC , 阿里巴巴高級技術專家李鈺分享,主要介紹 Flink 的容錯機制原理,內容大綱如下:
1.有狀態的流計算
2.全局一致性快照
3.Flink的容錯機制
4.Flink的狀態管理

一、有狀態的流計算

流計算

流計算是指有一個數據源可以持續不斷地發送消息,同時有一個常駐程序運行代碼,從數據源拿到一個消息後會進行處理,然後把結果輸出到下游。

分佈式流計算

分佈式流計算是指把輸入流以某種方式進行一個劃分,再使用多個分佈式實例對流進行處理。

流計算中的狀態

計算可以分成有狀態和無狀態兩種,無狀態的計算只需要處理單一事件,有狀態的計算需要記錄並處理多個事件。

舉個簡單的例子。例如一個事件由事件ID和事件值兩部分組成,如果處理邏輯是每拿到一個事件,都解析並輸出它的事件值,那麼這就是一個無狀態的計算;相反,如果每拿到一個狀態,解析它的值出來後,需要和前一個事件值進行比較,比前一個事件值大的時候才把它進行輸出,這就是一個有狀態的計算。

流計算中的狀態有很多種。比如在去重的場景下,會記錄所有的主鍵;又或者在窗口計算裏,已經進入窗口還沒觸發的數據,這也是流計算的狀態;在機器學習/深度學習場景裏,訓練的模型及參數數據都是流計算的狀態。

二、全局一致性快照

全局一致性快照是可以用來給分佈式系統做備份和故障恢復的機制。

全局快照

什麼是全局快照

全局快照首先是一個分佈式應用,它有多個進程分佈在多個服務器上;其次,它在應用內部有自己的處理邏輯和狀態;第三,應用間是可以互相通信的;第四,在這種分佈式的應用,有內部狀態,硬件可以通信的情況下,某一時刻的全局狀態,就叫做全局的快照。

爲什麼需要全局快照

  • 第一,用它來做檢查點,可以定期對全局狀態做備份,當應用程序故障時,就可以拿來恢復;
  • 第二,做死鎖檢測,進行快照後當前的程序繼續運行,然後可以對快照進行分析,看應用程序是不是存在死鎖狀態,如果是就可以進行相應的處理。

全局快照舉例

下圖爲分佈式系統中全局快照的示例。

P1和P2是兩個進程,它們之間有消息發送的管道,分別是C12和C21。對於 P1進程來說, C12是它發送消息的管道,稱作output channel; C21是它接收消息的管道,稱作 input channel。

除了管道,每個進程都有一個本地的狀態。比如說P1和P2每個進程的內存裏都有XYZ三個變量和相應的值。那麼 P1和P2進程的本地狀態和它們之間發送消息的管道狀態,就是一個初始的全局狀態,也可稱爲全局快照。

假設P1給P2發了一條消息,讓P2把x的狀態值從4改爲7,但是這個消息在管道中,還沒到達P2。這個狀態也是一個全局快照。

再接下來,P2收到了P1的消息,但是還沒有處理,這個狀態也是一個全局快照。

最後接到消息的P2把本地的X的值從4改爲7,這也是一個全局快照。

所以當有事件發生的時候,全局的狀態就會發生改變。事件包括進程發送消息、進程接收消息和進程修改自己的狀態。

2.全局一致性快照

假如說有兩個事件,a和b,在絕對時間下,如果a發生在b之前,且b被包含在快照當中,那麼則a也被包含在快照當中。滿足這個條件的全局快照,就稱爲全局一致性快照。

2.1 全局一致性快照的實現方法

時鐘同步並不能實現全局一致性快照;全局同步雖然可以實現,但是它的缺點也非常明顯,它會讓所有應用程序都停下來,會影響全局的性能。

3.異步全局一致性快照算法 – Chandy-Lamport

異步全局一致性快照算法Chandy-Lamport可以在不影響應用程序運行的前提下,實現全局一致性快照。

Chandy-Lamport的系統要求有以下幾點:

  • 第一,不影響應用運行,也就是不影響收發消息,不需要停止應用程序;
  • 第二,每個進程都可以記錄本地狀態;
  • 第三,可以分佈式地對已記錄的狀態進行收集;
  • 第四,任意進程都可以發起快照

同時,Chandy-Lamport算法可以執行還有一個前提條件:消息有序且不重複,並且消息可靠性可保障。

3.1 Chandy-Lamport算法流程

Chandy-Lamport的算法流程主要分爲三個部分:發起快照、分佈式的執行快照和終止快照。

發起快照

任意進程都可以發起快照。如下圖所示,當由P1發起快照的時候,第一步需要記錄本地的狀態,也就是對本地進行快照,然後立刻向它所有 output channel發送一個marker消息,這中間是沒有時間間隙的。marker消息是一個特殊的消息,它不同於應用之間傳遞的消息。

發出Marker消息後,P1就會開始記錄所有input channel的消息,也就是圖示C21管道的消息。

分佈式的執行快照

如下圖,先假定當 Pi接收到來自Cki的marker消息,也就是Pk發給Pi的marker消息。可以分兩種情況來看:

第一種情況:這個是Pi收到的第一個來自其它管道的marker消息,它會先記錄一下本地的狀態,再把 C12管道記爲空,也就是說後續再從 P1發消息,就不包含在此次快照裏了,與此同時立刻向它所有output channel發送marker消息。 最後開始記錄來自除Cki之外的所有input channel的消息。

上面提到Cki消息不包含在實時快照裏,但是實時消息還是會發生,所以第二種情況是,如果此前Pi已經接收過marker消息,它會停止記錄 Cki消息,同時會將此前記錄的所有Cki消息作爲Cki在本次快照中的最終狀態來保存。

終止快照

終止快照的條件有兩個:

  • 第一,所有進程都已經接收到marker消息,並記錄在本地快照;
  • 第二,所有進程都從它的n-1個input channel裏收到了marker 消息,並記錄了管道狀態。

當快照終止,快照收集器 (Central Server) 就開始收集每一個部分的快照去形成全局一致性快照了。

示例展示

在下圖的例子裏,一些狀態是在內部發生的,比如A,它跟其它進程沒有交互。內部狀態就是 P1發給自己消息,可以將A認爲是C11=[A->]。

Chandy-Lamport全局一致性快照的算法是怎麼執行的呢?

假設從p1來發起快照,它發起快照時,首先對本地的狀態進行快照,稱之爲S1,然後立刻向它所有的output channel,即P2和P3,分別發marker消息,然後再去記錄它所有input channel的消息,即來自P2和P3及自身的消息。

圖例所示,縱軸是絕對時間,按照絕對時間來看,爲什麼P3和P2收到marker消息會有時間差呢?因爲假如這是一個真實的物理環境裏的分佈式進程,不同節點之間的網絡狀況是不一樣的,這種情況會導致消息送達時間存在差異。

P3先收到marker消息,且是它接收到的第一個marker消息。接收到消息後,它首先會對本地狀態進行快照,然後把 C13管道的標記成 close,與此同時開始向它所有的output channel發送 marker消息,最後它會把來自除了C13之外的所有input channel的消息開始進行記錄。

接收到P3發出的marker信息的是P1,但這不是它接收的第一個marker,它會把來自C31 channel的管道立刻關閉,並且把當前的記錄消息做這個channel的快照,後續再接收到來自P3的消息,就不會更新在此次的快照狀態裏了。

接下來P2接收到來自P3的消息,這是它接到的第一個marker消息。接收到消息後,它首先對本地狀態進行快照,然後把 C32管道的標記成 close,與此同時開始向它所有的output channel發送 marker消息,最後它會把來自除了C32之外的所有input channel的消息開始進行記錄。

再來看P2接收到來自P1的消息,這不是P2接收到的第一個marker消息,所以它會把所有的 input channel全部關閉,並且記錄channel的狀態。

接下來看P1接收到來自P2的消息,這也不是它接收的第一個消息。那麼它就會把所有的input channel關閉,並把記錄的消息作爲狀態。那麼這裏面有兩個狀態,一個是C11,即自己發給自己的消息;一個是C21,是P2裏H發給P1D的。

最後一個時間點,P3接收到來自P2的消息,這也不是它收到的第一個消息,操作跟上面介紹的一樣。在這期間P3本地有一個事件J,它也會把J作爲它的狀態。

當所有進程都記錄了本地狀態,而且每一個進程的所有輸入管道都已經關閉了,那麼全局一致性快照就結束了,也就是對過去時間點的全局性的狀態記錄完成了。

3.3 Chandy-Lamport與 Flink之間的關係

Flink 是分佈式系統,所以 Flink 會採用全局一致性快照的方式形成檢查點,來支持故障恢復。Flink的異步全局一致性快照算法跟Chandy-Lamport算法的區別主要有以下幾點:

  • 第一,Chandy-Lamput支持強連通圖,而 Flink支持弱連通圖;
  • 第二,Flink採用的是裁剪的(Tailored)Chandy-Lamput異步快照算法;
  • 第三,Flink的異步快照算法在DAG場景下不需要存儲Channel state,從而極大減少快照的存儲空間。

三、Flink的容錯機制

容錯,就是恢復到出錯前的狀態。流計算容錯一致性保證有三種,分別是:Exactly once,At least once,At most once。

  • Exactly once,是指每條event會且只會對state產生一次影響,這裏的“一次”並非端到端的嚴格一次,而是指在 Flink內部只處理一次,不包括source和sink的處理。
  • At least once,是指每條event會對state產生最少一次影響,也就是存在重複處理的可能。
  • At most once,是指每條event會對state產生最多一次影響,就是狀態可能會在出錯時丟失。

端到端的Exactly once

Exactly once的意思是,作業結果總是正確的,但是很可能產出多次;所以它的要求是需要有可重放的source。
端到端的Exactly once,是指作業結果正確且只會被產出一次,它的要求除了有可重放的source外,還要求有事務型的sink和可以接收冪等的產出結果。

Flink的狀態容錯

很多場景都會要求在Exactly once的語義,即處理且僅處理一次。如何確保語義呢?

簡單場景的 Exactly Once 容錯方法

簡單場景的做法如下圖,方法就是,記錄本地狀態並且把 source的offset,即 Event log的位置記錄下來就好了。

分佈式場景的狀態容錯

如果是分佈式場景,我們需要在不中斷運算的前提下對多個擁有本地狀態的算子產生全局一致性快照。Flink 分佈式場景的作業拓撲比較特殊,它是有向無環並且是弱聯通圖,可以採用裁剪的Chandy-Lamport,也就是隻記錄所有輸入的offset和各個算子狀態,並依賴rewindable source(可回溯的source,即可以通過offset讀取比較早一點時間點),從而不需要存儲channel的狀態,這在存在聚合 (aggregation)邏輯的情況下可以節省大量的存儲空間。

最後做恢復,恢復就是把數據源的位置重新設定,然後每一個算子都從檢查點恢復狀態。

3.Flink 的分佈式快照方法

首先在源數據流裏插入Checkpoint barrier,也就是上文提到的Chandy-Lamport算法裏的marker message,不同的Checkpoint barrier會把流自然地切分多個段,每個段都包含了Checkpoint的數據;

Flink 裏有一個全局的Coordinator,它不像Chandy-Lamport對任意一個進程都可以發起快照,這個集中式的 Coordinator會把Checkpoint barrier注入到每個source裏,然後啓動快照。當每個節點收到barrier後,因爲 Flink 裏面它不存儲 Channel state,所以它只需存儲本地的狀態就好。

在做完了Checkpoint 後,每個算子的每個併發都會向Coordinator發送一個確認消息,當所有任務的確認消息都被Checkpoint Coordinator接收,快照就結束了。

4.流程演示

見下圖示,假設Checkpoint N 被注入到 source裏,這時source會先把它正在處理分區的offset記錄下來。

隨着時間的流逝,它會把Checkpoint barrier發送到兩個併發的下游,當barrier分別到達兩個併發,這兩個併發會分別把它們本地的狀態都記錄在Checkpoint 的裏:

最後barrier到達最終的subtask,快照就完成了。

這是比較簡單的場景演示,每個算子只有單流的輸入,再來看下圖比較複雜的場景,算子有多流輸入的情況。

當算子有多個輸入,需要把Barrier 對齊。怎麼把Barrier對齊呢?如下圖所示,在左側原本的狀態下,當其中一條barrier到達,另一條barrier命令上有的barrier還在管道中沒有到達,這時會在保證Exactly once的情況下,把先到達的流直接阻塞掉,然後等待另一條流的數據處理。等到另外一條流也到達了,會把之前的流unblock,同時把barrier發送到算子。

在這個過程中,阻塞掉其中一條流的作用是,會讓它產生反壓。Barrier 對齊會導致反壓和暫停operator的數據處理。

如果不在對齊過程中阻塞已收到barrier的數據管道,數據持續不斷流進來,那麼屬於下個Checkpoint的數據被包含在當前的Checkpoint裏,如果一旦發生故障恢復後,由於source會被rewind,部分數據會有重複處理,這就是at-least-once。 如果能接收at-least-once,那麼可以選擇其他可以避免barrier對齊帶來的副作用。另外也可以通過異步快照來儘量減少任務停頓並支持多個Checkpoint同時進行。

5.快照觸發

本地快照同步上傳到系統需要state Copy-on-write的機制。

假如對元數據信息做了快照之後數據處理恢復了,在上傳數據的過程中如何保證恢復的應用程序邏輯不會修改正在上傳的數據呢?實際上不同狀態存儲後端的處理是不一樣的,Heap backend會觸發數據的copy-on-write,而對於RocksDB backend來說LSM的特性可以保證已經快照的數據不會被修改。

四、Flink 的狀態管理

1.Flink 狀態管理

首先需要去定義一個狀態,在下圖的例子裏,先定義一個Value state。

在定義的狀態的時候,需要給出以下的幾個信息:

  • 狀態識別ID
  • 狀態數據類型
  • 本地狀態後端註冊狀態
  • 本地狀態後端讀寫狀態

2.Flink 狀態後端

又叫state backend,Flink狀態後端有兩種;

  • 第一種,JVM Heap,它裏面的數據是以Java對象形式存在的,讀寫也是以對象形式去完成的,所以速度很快。但是也存在兩個弊端:第一個弊端,以對象方式存儲所需的空間是磁盤上序列化壓縮後的數據大小的很多倍,所以佔用的內存空間很大;第二個弊端,雖然讀寫不用做序列化,但是在形成snapshot時需要做序列化,所以它的異步snapshot過程會比較慢。

  • 第二種,RocksDB,這個類型在讀寫時就需要做序列化,所以它讀寫的速度比較慢。但是它有一個好處,基於LSM的數據結構在快照之後會形成sst文件,它的異步checkpoint過程就是文件拷貝的過程,CPU消耗會比較低。

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

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