1. 程式人生 > >《Apache Flink官方文件》程式設計模型

《Apache Flink官方文件》程式設計模型

原文連結   譯者:魏勇

抽象層次

Flink 能夠為流式計算或批處理應用提供多種層次的抽象介面。

levels_of_abstraction

  • 最低階的抽象介面是狀態化的資料流介面。這個介面是通過 ProcessFunction 整合到 資料流 API 中的。此類介面讓使用者可以使用連續的容錯狀態,並且可以不受限制地處理多個數據流中的事件。另外,使用者也可以通過註冊事件時間和時間處理回撥函式的方法來實現複雜的計算程式。
  • 實際上,大部分程式通常會使用以資料流 API(有界/無界資料流)、資料集 API(有界資料集)為代表的核心 API,而並不會使用前述低階抽象介面。這些核心 API 為資料處理提供了大量的通用構建模組,包括使用者定義的各種各樣的變換、連線、聚集、視窗、狀態等等。在程式語言中,這些 API 處理的資料型別通常會表現為相應的類的形式。

由於資料流 API 集成了低階處理函式,因此可以通過資料流API為某些特定操作應用低階處理介面。此外,資料集 API 也為諸如迴圈、迭代之類的有界資料集提供了一些補充的程式設計原語。

  • 資料表 API 是一種以資料表為核心地宣告式 DSL,能夠動態地修改那些表徵資料流的表。資料表 API 的工作模式是一種(擴充套件的)關係型模型:每個資料表都依附於一個 schema(類似於關係型資料庫中的表結構),相應的 API 就可以實現很多類似的操作,例如 select,project,join,group by,aggregate,等等。資料表 API 程式定義的僅僅是如何在邏輯上實現各種程式操作,而不是直接指定程式程式碼執行的具體步驟。儘管資料表 API 可以通過各式各樣的自定義函式進行擴充套件,但是它在表達能力上仍然比不上核心 API,不過資料表 API 的優勢是在使用上更簡練(相對於核心 API 可以減少很多程式碼)。此外,資料表 API 程式在執行之前也會使用一個優化器對程式進行優化。

由於使用者可以在資料表與資料流/資料集之間進行無縫切換,程式也可以混合使用資料表 API 和資料流/資料集 API。

  • Flink 提供的最高階介面是 SQL。這個層次的抽象介面和資料表 API 非常相似,包括語法和介面的表現能力,唯一的區別是通過 SQL 查詢語言實現程式。實際上,SQL 抽象介面和資料表 API 的互動非常緊密,而且 SQL 查詢也可以在資料表 API 中定義的表上執行。

程式與資料流

Flink 程式的基礎構建單元是(資料)流與變換(注意,資料集 API 中使用的資料集也是一種內建的流,這一點我們以後會細說)。顧名思義,一個數據流就是一組資料記錄組成的(可能永遠不會停止的)流,而變換就是一種接受若干資料流作為輸入,然後再輸出結果資料流的過程。

Flink 程式在執行的時候會被對映到資料流圖中,這個資料流圖就是由程式中的資料流和相應的變換操作組成的。資料流圖開始於一個或多個數據源(source),結束於另外一些匯聚點(sink)。資料流圖類似於有向無環圖(DAG)。雖然可以通過迭代構造器生成某些特殊形式的環,但為了簡化說明,大部分情況下我們不考慮這種結構。

program_dataflow

通常情況下程式中的變換和資料流圖中的運算子是一一對應的。不過有的時候也會出現一個變換由多個變換運算子組成的情況。

資料來源和匯聚點的相關文件在資料流聯結器批處理聯結器的說明文件中。變換的相關文件在資料流變換資料集變換的說明文件中。

併發資料流圖

本質上說,Flink 程式是分散式、併發執行的。在程式執行過程中,一個數據流可能會有一個或多個流分割槽,而一個運算子也可能會有一個或多個運運算元任務。每個運運算元任務與另外一個運運算元任務之間都是相互獨立的,他們是在不同的執行緒中執行的,甚至有可能所執行的機器或者容器都完全不同。

運運算元任務的數量由運算子的併發數確定。資料流的併發數就是它所生成的運算子的個數。程式中不同的運算子可以有不同等級的併發量。

parallel_dataflow

在兩個運算子之間傳輸資料流既可以使用一對一的直接型模式,也可以使用重分發模式:

  • 一對一 模式的資料流(例如上圖中 Source 和 map() 運算子之間的資料流)中元素的分組和順序會保持不變,也就是說,map() 運算子的子任務[1]所看見的元素與 Source 運算子的子任務[1]所生成的元素的順序完全一致。
  • 重分發 模式的資料流(例如上圖中 map() 和 keyBy/window 運算子之間的資料流,以及 keyby/window 和 Sink 運算子之間的資料流)會改變資料流所在的分割槽。根據所選的變換的不同,每個運運算元任務會將資料傳送到不同的目標子任務中去。keyBy()(通過對 key 進行雜湊計算來重分割槽)、boradcast() 和 rebalance()(隨機重分割槽)就是重分發模式的幾個例子。在重分發模式下,元素之間的先後次序在每對傳送——接收子任務(例如 map() 的子任務[1]和 keyBy/window 的子任務[2])中是保持不變的。因此,在上圖的例子中,儘管在子任務之間每個 key 的順序都是確定的,但是由於程式的併發過程引入了不確定性,最終到達 Sink 的元素順序就不能保證與一開始的元素順序完全一致。

關於配置併發的更多資訊可以參閱併發執行文件。

視窗

計數(counts)、求和(sums)等聚合事件和批處理過程的工作模式完全不同。舉個例子,由於資料流在理論上是無限的,因此直接計算資料流中的所有元素的個數基本上是無法實現的。因此,資料流的聚合操作(計數、求和等)都是由視窗(window)限定了範圍的,例如“計算前五分鐘的元素個數”,“對前100個元素求和”等。

視窗可以通過時間(例如以30秒為單位)或者資料(例如以100個元素為單位)來定義。有多種不同型別的視窗,例如資料不重疊的滾動視窗(tumbling window)、資料重疊的滑動視窗(sliding window),以及以非活動狀態為間隔的會話視窗(session window)。

windows

這篇文章介紹了很多視窗的例子。另外,也可以查閱視窗文件瞭解更多內容。

時間

流式計算程式中的時間概念(例如在定義視窗時經常會用到時間)有以下幾種含義:

  • 事件時間(Event Time),是指事件建立時的時間。這種型別時間一般會表示為事件的時間戳,再通過事件生成感測器或者事件生成服務等附到事件中。Flink 通過時間戳指定器獲取事件的時間戳。
  • 攝入時間(Ingestion Time),是指事件在源運算子中進入Flink的資料流的時間。
  • 處理時間(Processing Time),是指運算子在執行時間類操作時的本地時間。

event_ingestion_processing_time

關於處理時間的更多資訊請參閱事件時間文件

有狀態操作

雖然資料流中有很多運算子每次只需要考慮當前所處理的唯一的事件(例如事件分析器),但是仍然存在很多需要記錄多個事件的資訊的場景(視窗操作符就是個很好的例子),這種需要記錄資訊的操作就稱為有狀態的操作。

有狀態操作的狀態可以理解成是以鍵值對(key/value)形式儲存的。這個狀態的分割槽和分發過程是和資料流嚴格繫結在一起的,隨後有狀態運算子讀取資料流就可以獲取狀態了。因此,在 keyBy() 函式執行之後,只能在帶鍵的資料流中訪問 key/value 狀態,而且也只能獲取與當前事件的主鍵相對應的值。資料流的鍵和值的對應確保了所有狀態更新都是本地操作,同時也保證了事務的一致性。這個對應也使得Flink可以透明地重分發狀態,並調整資料流地分割槽。

有關狀態地更多內容請參閱有狀態操作文件。

容錯性檢查點

Flink 通過資料重發和校驗檢查機制相結合的方式實現了容錯能力。檢查點和運算子中的相應的狀態一樣直接關聯到輸入資料流中的特定的某個點。為了維護資料一致性(一次處理的語義),可以讓資料流從檢查點恢復,這是通過恢復運算子的狀態並對檢查點對應的事件進行重發的方式實現的。

檢查點區間是對程式的容錯能力與恢復時間(需要重發的事件數量)的折衷。

容錯區間文件中有關於Flink如何處理檢查點以及其他相關主題的詳細說明。更多關於配置啟用檢查點的資料請參閱檢查點API文件

批處理操作

Flink 將批處理程式看成流式計算程式的一種有界資料流(即元素數量是可數的)的特例。這裡,資料集(DataSet)也被看作一種資料流。因此,上面流式計算程式中的很多概念也能應用到批處理程式中,除了以下幾處不同:

  • 批處理程式的容錯性不使用檢查點機制。由於輸入資料本身是有界的,批處理的恢復是通過完全重發所有資料流實現的。這樣,恢復過程中的開銷可能更大一些,但是由於沒有了檢查點,正常處理過程的開銷反而更小了點。
  • 資料集API中的有狀態操作沒有使用鍵/值(key/value)索引結構,而是使用了簡化的記憶體/外存資料結構,
  • 資料集API引入了特殊的同步(基於超步演算法的)迭代介面,該介面僅能用於有界資料流。更多內容請參考迭代文件