Snapshot Algorithm
分佈式快照算法是拿來幹嘛的?
在缺乏全局時鐘或者全局時鐘不可靠的分佈式系統確定全局狀態
A snapshot algorithm is used to create a consistent snapshot of the global state of a distributed system. Due to the lack of globally shared memory and a global clock, this isn’t trivially possible.
Global Snapshot
全局快照我們也可以理解爲全局狀態,主要用於在Failure Recovery。
我們把分佈式系統簡化爲以節點表示的進程狀態圖,進程之間以消息隊列進行通信。
這裏的消息隊列有兩類input channel 和 output channel,channel可以看作是一個無限大的FIFO隊列。
隊列中收到的message都是有序無重複的。
C-L分佈式快照算法通過記錄每個進程的local state和input channel中有序的message作爲一個局部快照。
那麼global snapshot就是把所有進程的local snapshot全部合併起來
Chandy-Lamport
考慮一個分佈式系統,這些進程運行在不同的物理機器。一個分佈式系統全局狀態就是進程的狀態和隊列中的message
考慮一個分佈式系統,其中有兩個節點,即兩個進程再循環傳遞令牌。p和q,s0,s1對應的是兩個狀態。token可以理解爲一個令牌,全局只有一個,進程之間通過消息隊列發送令牌
s0表示進程不持有令牌,s1表示進程持有令牌。
兩個進程之間的連線代表消息隊列,token可能處於消息隊列中,這時候兩個進程狀態都是s0,都沒有令牌。
進程是有可能崩潰的
我們要保證進程崩潰重啓之後,系統仍然能夠正常運行,於是我們需要從某個檢查點恢復程序的運行狀態,這就需要定時把系統中在某個時間點的狀態保存起來,也就是做一次snapshot
保存什麼東西?
保存每個節點在當前的狀態以及消息隊列在當前的狀態(消息隊列的message)。
eg:
對上圖來說,我們對右上角的狀態做一個snapshot,保存的狀態如下:
p | s0 |
q | s0 |
p->q | token |
q->p | empty |
但是由於pq是兩個進程,時間不同步,例如p進程在發送token之後進行了快照存儲,q在p發送token之前進行snapshot.
1.p在snapshot的時候由於p已經發送了token,現在token在q進程隊列中,所以p進程保存快照的時候認定token既不在p也在p的接受隊列
2.q在p發送之前快照,這時候token還在p手上,因此q進程認定token不在q也不在q進程的隊列中
導致全局快照中token消失不見
解決辦法
Marker-Receiving Rule
p發送完token後發起snapshot,發送marker給q,q收到marker就保存本地狀態。由於queue是FIFO的,q先接收到token,再接收到marker,所以保存狀態的時候肯定會知道自己已經拿了token,所以記錄自己爲s1.並且p->q=empty
然後q再發送marker給p,p接收到marker,檢查p保存狀態後到接收到marker之前有沒有收到信息,由於沒有收到,所以設置q->p爲empty
這就是保存了右下角圖片的狀態。
接下來具體介紹一下CL算法的流程,主要三個部分:
1.initiate a snapshot 系統中任意一個進程發起一個snapshot
2. Propagting a snapshot 系統中其他進程逐個創建snapshot
3. Terminating a snapshot 算法結束
-
Initiating a snapshot
1.進程Pi發起,記錄自己的進程狀態,同時產生一個標誌信息marker,marker和進程通信的message不同
2.將marker信息通過output channel發送到其他系統的進程
3.記錄所有從input channel接收到的message -
Propagting a snapshot
1.進程Pj從input channel接收到marker信息
2.如果Pj還沒有記錄到自己的進程狀態,則:
Pj記錄自己的進程狀態,同時將 channel C 置爲空,並且向 output channel 發送 marker 信息
3.記錄其他 channel 在收到 marker 之前的 channel 中收到所有 message
啥意思?
比如 Pj 做完 local snapshot 之後 Ckj 中發送過來的 message 爲 [a,b,c,marker,x,y,z] 那麼 a, b, c 就是進程 Pk 做 local snapshot 前的數據,Pj 對於這部分數據需要記錄下來,比如記錄在 log 裏面。而 marker 後面 message 正常處理掉就可以了。
- Terminating a snapshot
所有的進程都收到 marker 信息並且記錄下自己的狀態和 channel 的狀態(包含的 message)
例子:藍色表示全局snapshot
初始狀態如上:
- P1發起全局snapshot記錄
- P1先記錄本身的進程狀態,然後向P2發送marker信息
- 在marker信息到達P2之前,P2向P1發送message: M
- P2 收到 P1發送過來的marker信息之後,記錄自己的狀態。
- 然後 P1 收到 P2 之前發送過來的 message: M。
- 對於 P1 來說,從 P2 channel 發送過來的信息相當於是 [M, marker],由於 P1 已經做了 local snapshot,所以 P1 需要記錄 message M。
所以我們就保持了全局snapshot的狀態
Flink 分佈式快照機制
flink通過使用分佈式快照來提供容錯服務。這些快照就充當checkpoint,系統在發生故障時候就回滾。
當某個節點宕機之後,flink將會停止分佈式數據流。然後系統會重新啓動操作算子並且將其重新設置爲最新的checkpoint.
輸入流將重置爲狀態快照記錄的位置,作爲重新啓動的並行數據流的一部分。
flink的checkpint就是基於分佈式快照實現的。
- checkpoint 與 state
checkpoint 在flink中指的是一個執行操作,最終產生結果作爲分佈式快照提供容錯機制
state 構成checkpoint的數據構成,指的是流式計算中持久化的狀態
checkpoint執行機制
ckpoint執行流程:
最後Coordinator把完整的checkpoint數據存儲到checkpoint coordinator.
checkpoint執行流程
- checkpoint僅支持flink在內部實現EXACTLY ONCE 語義,端到端的exactly once需要source 和sink的支持
但是講到這裏還是很懵
barriers是什麼鬼?
barriers
-
barriers被注入數據流並與記錄一起作爲數據流的一部分,向前流動
-
barriers永遠不會超過記錄,數據流保證嚴格有序
-
barriers把數據流中的記錄分爲當前進入快照的記錄和下一個快照的記錄
-
每個barrier都有快照的ID,並且barrier之前的記錄都進入了該快照。
-
barrier不會中斷流的流動,非常輕量級
-
來自不同快照的多個barrier可以同時在流中出現,也就是不同的快照可以同時進行
-
快照n的barrier被插入的位置是快照n所有數據最大的數據,eg: kafka最後一條記錄的偏移量。並且最後會把這個位置報告給checkpoint協調器。
-
然後barrier向下流動,當一個算子從所有輸入流都受到快照n的barrier的時候,算子就會爲快照n發出barrier,進入到其所有輸出流中。
-
一旦sink操作算子從其所有輸入流接收到barrier n,它就會向checkpoint協調器確認快照n完成。
-
所有sink確認快照後,意味着快照已經完成
一旦完成快照n,job不會再去請求快照n之前的記錄,因爲這些記錄已經通過整個數據流拓撲,就是被處理結束了。
-
一旦操作算子從輸入流接收到快照barrier n,就不能夠處理來自該流的其他任何記錄。知道從其他輸入接收到barrier n爲止 否則會搞混快照n和快照n+1的記錄
-
barrier n 所屬於的數據流暫時會被擱置,從這些流接受的記錄不會被處理,而是放入輸入緩衝區
-
一旦從最後一個流接收到barrier n,操作算子就會發出所有掛起的向後傳送的記錄,然後再自己發出快照n的barrier
-
之後會恢復處理所有來自輸入流的記錄,並且會優先處理來自輸入緩衝區的記錄
這就是checkpoints的對齊機制
state
當運算符包含任何形式的狀態的時候,快照也應該包含這個狀態。
狀態 state具體指的是?
1.系統狀態 :作爲運算符計算一部分的數據緩衝區,典型例子是窗口緩衝區。系統在其中收集窗口裏的記錄,直到窗口被計算或者拋棄
2.用戶定義的狀態 :可以理解爲算子map,filter等
3.操作算子在從輸入流接收到所有快照barrier的時候,以及向其輸出流發出barrier之前,會對其狀態進行寫快照。這個時候,在barrier之前的數據的狀態更新已經完成。barrier之後的數據不會更新狀態。
4.快照的狀態佔的空間很大,因此可以存儲在可配置的狀態後端,例如JobManager的內存。在生產環境下,也可以存儲在HDFS中。
5.存儲狀態之後,操作算子確認checkpoint完成,會把快照barrier發送到輸出流中,然後繼續。
異步狀態快照
上面介紹的快照過程是同步過程,當操作算子把狀態快照存儲到狀態後端的時候,會停止處理輸入的記錄。每次寫快照的時候,延遲會比較大。
我們可以把這個過程改爲異步,具體做法爲
操作算子必須能夠生成一個狀態對象,該狀態對象應以某種方式存儲,以便對操作算子狀態的進一步修改不會影響該狀態對象。copy-on-write是一種解決方法。
至於copy-on-write是什麼,請google,這裏不贅述。
輸入checkpoint的barrier之後,操作算子啓動其狀態的異步快照複製。會立刻釋放其barrier到輸出,並繼續進行常規流處理。複製過程是放在後臺完成的,它會向checkpoint協調器(JobManager)確認快照已經完成。
checkpoint僅僅在所有sink都已經收到barrier並且所有有狀態操作算子都確認完成了備份之後纔算完成。
Recovery
當失敗時,Flink選擇最新完成的checkpoint k。 然後,系統重新部署整個分佈式數據流,併爲每個操作算子重置作爲checkpoint k的一部分的快照的狀態。 數據源設置爲從位置Sk開始讀取。 例如在Apache Kafka中,這意味着告訴消費者從偏移量Sk開始讀取。