Flink checkpoint實現算法的理論基礎:Lightweight Asynchronous Snapshots for Distributed Dataflows

摘要:

分佈式有狀態的流式處理,讓我們可以在雲上部署和執行大規模持續計算,並實現了低延遲和高吞吐的目標。這種模式最基本的挑戰之一是當發生了潛在故障,系統依舊提供正確的處理保證。當前的方法都依賴於週期性的全局快照,在故障時恢復數據。這些方法主要有兩個缺點。第一,這些方法經常讓整體計算停頓,影響數據攝入。第二,這些方法渴望於保存操作狀態變化的所有記錄。這種行爲會導致產生超大量的快照信息,而這些快照信息並不是必須的。

本文提出了一種Asynchronous Barrier Snapshotting(ABS)方法。ABS是一種適合於主流數據流處理引擎的輕量級算法,且算法佔用了極小的空間。ABS算法在無環的執行拓撲結構中,只保存了操作狀態;而在有環的數據流中,保存了很小的記錄日誌。Apache Flink是一個分佈式數據分析引擎,並支持有狀態的數據流處理。我們在其上實現了ABS算法。在我們評估中顯示,ABS算法沒有對執行效率產生很大的影響,保持了先行擴展能力以及在頻繁的快照操作時依舊能夠良好的運行。

關鍵字: 容錯,分佈式計算,流處理,數據流,雲計算,狀態管理

1 引言

分佈式數據流處理對於數據密集型計算來說是一種新興的範式。這種範式需要能滿足對於大量數據的持續計算,並能夠實現高吞吐低延時的目標。時序性要求很強的應用,尤其是實時性分析領域的應用,可以充分利用流計算系統的特性。這種流計算系統有Apache Flink,Naiad等等。由於在真實的使用場景中錯誤是不能容忍的,所以容錯是這些系統的核心問題。目前已知能夠滿足有狀態的流計算系統的exactly-once語義的方法依賴於對於執行狀態的全局持續快照。但是這樣做有兩個缺點影響實時流計算的時效性。同步快照技術讓分佈式計算的全體節點都停止運行,從而在全局獲得了一個一致的狀態。進一步說,就我們所知目前所有的分佈式快照算法,都是通過通道或未處理消息的方式貫穿於執行圖中,作爲快照狀態的一部分。通常情況下,這些數據比需要的大很多。

在本文的算法中,我們致力於在不影響性能的條件下,提供輕量級快照方案,尤其適合分佈式有狀態的數據流系統。我們的方案提供了異步狀態快照,佔用更小的空間,只保存了非循環執行拓撲操作狀態。另外我們也考慮了循環執行拓撲的情況。通過使用拓撲圖選中部分下行備份,從而保持快照狀態最小化。我們的技術並沒有讓流操作掛起,且只佔用了很小一部分的運行時開銷。本文的主要成果可以概括成以下幾點:

  • 我們提出並實現了一個異步快照算法,能夠在無環圖執行圖中使用很小的快照。
  • 我們描述並實現了在有環執行圖的情況下的算法。
  • 我們展示了算法在Apache Flink Streaming的應用和對比。

本文餘下的部分組織結構如下:第二部分概述了當前在有狀態數據流系統中的分佈式全局快照算法。第三部分提供了Apache Flink處理模型的概覽。第四部分詳細描述了全局快照算法的主要方法。在第五部分,我們描述了恢復機制。最後,在第六部分對我們的工作進行了總結,第七部分是評估信息以及第八部分的展望和結論。

2 相關工作

在過去的數十年裏,人們提出了若干個持續計算的恢復機制。將持續計算仿真成無狀態的分佈式批處理系統依賴於狀態重算,例如DiscretizedStreams和Comet。另外,有狀態的流計算系統使用檢查點來保存全局的一致快照,從而實現錯誤恢復,例如Naiad,SDGs,Piccolo和SEEP。

分佈式環境的全局一致性快照的問題已經在過去的幾十年內被廣泛的研究分析,例如Chandy和Lamport。理論上來說一個全局快照反映了一次執行的所有狀態,或者是一個特定實例的一次操作狀態。Naiad提供了一個簡單但是代價很高的方法。這個方法通過三步創建一個同步快照:首先將執行圖上所有的計算掛起,然後進行快照,最後在全局快照結束的時候引導所有的任務繼續執行他的操作。這個方法對於吞吐量和空間的要求都很高,因爲需要阻塞整體的計算,同時依賴於上行備份在生產者端的記錄。另外一個由Chandy和Lamport提出的流行方法在許多的系統中應用。系統在進行上行備份的時候執行異步快照。這種方法通過在執行圖插入分佈式標籤,從而觸發算子和通道狀態的持久化。這個方法的上行備份同樣需要佔用大量的空間,且備份記錄的計算會導致更高的恢復時間。我們的方法擴展了Chandy和Lamport的異步快照思想,同時對於無環圖不會有備份日誌記錄,對於有環圖保存可選擇的備份記錄。

3 背景:Apache Flink

我們當前的工作以Apache Flink Streaming的容錯機制所導向。Apache Flink Streaming是Apache Flink Stack的一部分。Apache Flink系統框架提供批處理Job和流計算Job統一處理框架。每個Job都是由多個有狀態且互相通信的task組成。Flink中的分析工作被編譯成task的有向圖。Job從系統外部獲取數據,並以流水線的方式在task圖中流轉。task將根據接收到的輸入數據持續計算內部狀態,併產生新的輸出數據。

3.1 流式計算模型

用於流處理的Apache Flink API允許複雜流分析Job的組合,並使用公開無界分區數據流作爲它的核心數據。這些被抽象成DataStream對象。DataStream可以通過外部數據源創建,或者由其他的DataStream喚起。DataStream支持若干個算子(operator),例如map,filter以及reduce。 它們以高階函數的形式應用於每條記錄並生成新的數據流。每個算子可以通過放到並行的實例上運行,分別負責自己對應的數據流,從而實現算法的並行。這就實現了分佈式流計算。

示例1的代碼展示了怎樣用Apache Flink實現一個簡單的累加計算單詞數目的方法。在這個程序中,我們從一個text文件中讀取單詞,並在標準輸出打印每個單詞的個數統計。在程序中,需要記錄文件的讀取位置以及內部計數器需要記錄單詞的個數。這些表明這是一個有狀態的流計算程序。
在這裏插入圖片描述

1 val env : StreamExecutionEnvironment = ...
2 env.setParallelism(2)
3
4 val wordStream = env.readTextFile(path)
5 val countStream = wordStream.groupBy(_).count
6 countStream.print

3.2 分佈式數據流的執行

當一個用戶執行一個應用,DataStream的所有算子都被編譯到一個執行圖中。類似於Naiad的概念,執行圖是一個有向圖G=(T,E)。端點T代表所有的任務,邊E代表人物之間的通道。圖1展示了上面示例的執行圖。如圖所示,每個算子的示例都封裝到對應task中。當沒有輸入源,則可以將task看做數據源;當沒有輸出通道,則可以將task看做sink。進一步講,M標識所有task在並行執行期間的記錄。用t來表示task,每個t都屬於T,代表一個算子的獨立執行實例。t由以下部分組成:(1)輸入通道It,輸出通道Ot的集合,且It,Ot屬於E;(2)一個算子的狀態St。(3)一個用戶定義的方法(UDF)ft。數據攝取是採用拉的方式:在執行階段,每個task消費輸入數據,更新自己的算子狀態,並生成新的數據傳輸給用戶定義的函數。更明確的說,每個記錄r屬於M,T中的任務t接收到記錄r,t的狀態變成St*,t生成新的數據記錄D,並將D傳遞給用戶自定義函數ft,D屬於M。ft:
在這裏插入圖片描述

4 異步柵欄快照(Asynchronous Barrier Snapshotting,ABS)

爲了提供一致性結果,分佈式處理系統需要能夠恢復失敗的task。週期性的獲取執行圖的快照,並在失敗時進行恢復操作,是一種提供恢復能力的方法。一個快照是執行圖的全局狀態,捕獲了所有重啓到指定執行狀態所必須的信息。

4.1 問題定義

我們定義全局快照
在這裏插入圖片描述
是執行圖G = (T,E)所有task和邊的狀態。更詳細的說,T包含所有的算子狀態
在這裏插入圖片描述
E
包含了所有通道的狀態
在這裏插入圖片描述
其中e包含了數據記錄。
我們需要每個快照G
都保持這些信息,從而能夠確保在終止後的恢復操作結果正確。
如果所有進程都是活動的,則在啓動後的有限時間內結束。可行性表示快照的意義,即在快照過程中沒有丟失關於計算的信息。從形式上講,這意味着在快照中維護了因果順序[9],以便在任務中交付的記錄也從快照的角度發送。

4.2 無環數據流ABS

當執行被分爲若干個階段(stage),那麼我麼就有可能不保存通道狀態的情況下做快照。stage將注入的數據流拆分開,並把分開的數據和他們相關的計算結合到一起形成一系列的執行。在這些執行中,所有先前輸入和產生的結果已經被充分處理。每個stage結尾的operator狀態的集合反映了整個執行歷史,因此可以作爲快照信息。我們算法的核心理念是,當持續數據集成時,通過分期快照創建一致快照。
在我們的方法中,通過使用特殊的柵欄標籤週期的切入數據流中,從而將持續數據流拆分成不同的分期階段。這個操作會貫通整個執行圖,一直到sink節點。全局快照將會在task接收到代表分期的柵欄標籤時,增量的進行構建。對於算法,我們做了如下假設:

在這裏插入圖片描述
算法1 無環執行圖的異步柵欄快照

1: upon event hInit | input channels, out-
put channels, fun, init statei do
2: state := init state; blocked inputs := / 0;
3: inputs := input channels;
4: outputs := output channels; udf := fun;
5:
6: upon event hreceive | input, hbarrierii do
7: if input 6= Nil then
8: blocked inputs := blocked inputs ∪
{input};
9: trigger hblock | inputi;
10: if blocked inputs = inputs then
11: blocked inputs := / 0;
12: broadcast hsend | outputs, hbarrierii;
13: trigger hsnapshot | statei;
14: for each inputs as input
15: trigger hunblock | input i;
16:
17:
18: upon event hreceive | input, msgi do
19: {state 0 ,out records} := udf(msg,state);
20: state := state 0 ;
21: for each out records as {output,out record}
22: trigger hsend | output, out recordi;
23:
24:
  • 網絡通道是可信的。一個FIFO的消息時可以被blocked和unblocked。當一個通道blocked,所有的消息將會被緩存,不被傳遞。當通道unblocked,則消息會繼續傳播。
  • task可以觸發其通道的操作,比如block,unblock以及發送消息。也支持向所有的輸出通道廣播消息。
  • 源數據task通過Nil的輸入通道注入消息。

ABS算法1執行步驟如下:一箇中心協調者週期的向所有源task注入階段柵欄。當一個源task接收到柵欄,他將會爲當前狀態做一個快照,之後將柵欄消息光波導所有的輸出通道(如圖2)。當一個非源task從一個輸入通道接收到柵欄消息,將會阻塞這個通道,直到所有的輸入通道都接受到這個柵欄消息。這時,task會對當前的狀態執行一個快照,並將柵欄消息廣播到所有的輸出通道。之後task將會unblock所有的輸入通道,繼續進行計算操作。全局快照 G ∗ = (T ∗ ,E ∗ )將會在所有的算子狀態T都完成,且E=空的時候完成。

證明草圖:誠如之前介紹的快照算法應該支持可中執行和可行性。可終止性通過通道和無環執行圖的屬性來保證。通道的可靠性確保只要task還運行,每個柵欄消息最終都會被接收。進一步說,一定存在從源開始,經歷拓撲圖中所有的task,直到結束的路徑存在。

對於可行性,只要能夠讓全局快照的算子狀態反映最後階段的歷史數據就可以。這需要通道的FIFO特性以及輸入通道在接到柵欄消息的阻塞特性實現的。
在這裏插入圖片描述

4.3 有環數據流ABS

在有向有環圖的執行圖中,ABS算法將不會停止,最終導致死鎖。原因是環中的task會無限的等待所有輸入通道的柵欄消息。
下圖爲算法。由於實際工作中不涉及,所以忽略這部分。

Algorithm 2 Asynchronous Barrier Snapshotting for
Cyclic Execution Graphs
1: upon event hInit | input channels,
backedge channels, output channels, fun,
init statei do
2: state := init state; marked := / 0;
3: inputs := input channels; logging := False;
4: outputs := output channels; udf := fun;
5: loop inputs := backedge channels;
6: state copy := Nil; backup log := [];
7:
8: upon event hreceive | input, hbarrierii do
9: marked := marked∪{input};
10: regular := inputs\loop inputs;
11: if input 6= Nil AND input / ∈ loop inputs then
12: trigger hblock | inputi;
13: if ¬logging AND marked = regular then
14: state copy := state;logging := True;
15: broadcast hsend | outputs, hbarrierii;
16: for each inputs as input
17: trigger hunblock | input i;
18:
19: if marked = input channels then
20: trigger hsnapshot | {state copy,
backup log}i;
21: marked := / 0;logging := False;
22: state copy := Nil;backup log := [];
23:
24: upon event hreceive | input, msgi do
25: if logging AND node ∈ loop inputs then
26: backup log := backup log :: [input];
27: {state 0 ,out records} := udf(msg,state);
28: state := state 0 ;
29: for each out records as {output,out record}
30: trigger hsend | output, out recordi;
31:
32:

5 錯誤恢復

雖然這部分內容不是工作的中點,但是有效的錯誤恢復機制可以促進我們快照算法的引用。因此我們對錯誤恢復的操作方式提供了簡要的描述。對於一致性快照,這裏有若干個錯誤恢復機制。最簡單的機制是從最後一個全局快照重啓整個執行圖:(1)每個任務都從持久化存儲獲得快照St,並將自己的狀態恢復到St狀態。(2)恢復備份日誌和處理所包含的數據。(3)開始從輸入通道採集數據。

另一種是類似於TimeStream的局部圖恢復機制。本機制重新調度失敗任務的上行task,直到根節點。一個恢復計劃示例如圖4。爲了滿足exactly-once語義,下行節點收到的重複數據應該被忽略,從而避免重複計算。爲了達到此目的,我們可以遵從類似於SDG機制,從數據源開始爲每個數據都進行編號。因此,每個下行節點都可以忽略掉編號小於已處理編號的數據。
在這裏插入圖片描述

6 實現

我們將ABS算法的實現貢獻給Apache Flink,從而爲其提供了exactly-once執行語義的實現。在我們目前的實現中,將所有blocked通道的輸入數據都存儲在了磁盤上,而不是保存在內存中。這樣增加了可擴展性。雖然這種方式增加了程序的魯棒性,但是卻影響了ABS算法的運行時性能。

爲了區分算子的狀態和數據,我們採用了一個明確的接口:OperatorState。接口包括更新和核對狀態的方法。我們爲Apache Flink有狀態的運行時算子提供了OperatorState的實現,如基於偏移量的源或聚合。

快照協作作爲一個進程運行在Job manager上,它爲一個獨立的job執行圖保持了全局狀態。協調者將階段柵欄週期的切入執行圖的所有源數據中。依據不同的配置,上一個全局快照狀態存放在算子中,或一個分佈式的內存持久化數據庫中。

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