Flink之數據流編程模型(上)

數據流編程模型

在這裏插入圖片描述
Statefule Stream Processing: 是最低級別(底層)的抽象,只提供有狀態的流。它通過ProcessFunction嵌入到DataStream API之中。它使得用戶可以自由處理來源於一個或者多個流的事件。

DataStream/DataSet API: 在我們的實際工作中,大多數的應用程序是不需要上文所描述的低級別(底層)抽象,而是相對於諸如DataStream API(有界/無界流)和DataSet API(有界數據集)的Core API進行編程。

Table API:是圍繞着table的申明性DSL,可以被動態的改變(當其表示流時)。Table API遵循(擴展)關係模型:表有一個模式鏈接(類似與在關係數據庫中的表),API也提供了一些類似的操作:select, project, join, group-by, aggregate等。

表和DataStream / DataSet之間可以無縫轉換,允許程序混合使用Table API和DataStream 和DataSet API。

流式編程

用戶實現的Flink程序是由Stream和Transformation這兩個基本構建塊組成:Stream是一箇中間結果數據,而Transformation是一個操作,它對一個或多個輸入Stream進行計算處理,輸出一個或多個結果Stream。

當一個Flink程序被執行的時候,它會被映射爲Streaming Dataflow。一個Streaming Dataflow是由一組Stream和Transformation Operator組成,它類似於一個DAG圖,在啓動的時候從一個或多個Source Operator開始,結束於一個或多個Sink Operator。 下面是一個由Flink程序映射爲Streaming Dataflow的示意圖,如下所示:
在這裏插入圖片描述

上圖中,FlinkKafkaConsumer是一個Source Operator,map、keyBy、timeWindow、apply是Transformation Operator,RollingSink是一個Sink Operator。

並行數據流

在Flink中,程序天生是並行和分佈式的:一個Stream可以被分成多個Stream分區(Stream Partitions),一個Operator可以被分成多個Operator Subtask,每一個Operator Subtask是在不同的線程中獨立執行的。一個Operator的並行度,等於Operator Subtask的個數,一個Stream的並行度總是等於生成它的Operator的並行度。

在這裏插入圖片描述
上圖Streaming Dataflow的並行視圖中,展現了在兩個Operator之間的Stream的兩種模式:

  • One-to-one模式
    比如從Source[1]到map()[1],它保持了Source的分區特性(Partitioning)和分區內元素處理的有序性,也就是說map()[1]的Subtask看到數據流中記錄的順序,與Source[1]中看到的記錄順序是一致的。
  • Redistribution模式
    這種模式改變了輸入數據流的分區,比如從map()[1]、map()[2]到keyBy()/window()/apply()[1]、keyBy()/window()/apply()[2],上游的Subtask向下遊的多個不同的Subtask發送數據,改變了數據流的分區,這與實際應用所選擇的Operator有關係。
    另外,Source Operator對應2個Subtask,所以並行度爲2,而Sink
    Operator的Subtask只有1個,故而並行度爲1。

執行鏈

在Flink分佈式執行環境中,會將多個Operator Subtask串起來組成一個Operator Chain,實際上就是一個執行鏈,每個執行鏈會在TaskManager上一個獨立的線程中執行,如下圖所示:
在這裏插入圖片描述

上圖中上半部分表示的是一個Operator Chain,多個Operator通過Stream連接,而每個Operator在運行時對應一個Task;圖中下半部分是上半部分的一個並行版本,也就是對每一個Task都並行化爲多個Subtask。

窗口

Flink將Window分爲兩類,一類叫做Keyed Window,另一類叫做Non-Keyed Window。

標題Keyed Window

stream
       .keyBy(...)               <-  keyed versus non-keyed windows
       .window(...)              <-  required: "assigner"
      [.trigger(...)]            <-  optional: "trigger" (else default trigger)
      [.evictor(...)]            <-  optional: "evictor" (else no evictor)
      [.allowedLateness(...)]    <-  optional: "lateness" (else zero)
      [.sideOutputLateData(...)] <-  optional: "output tag" (else no side output for late data)
       .reduce/aggregate/fold/apply()      <-  required: "function"
      [.getSideOutput(...)]      <-  optional: "output tag"

Non-Keyed Windows

stream
       .windowAll(...)           <-  required: "assigner"
      [.trigger(...)]            <-  optional: "trigger" (else default trigger)
      [.evictor(...)]            <-  optional: "evictor" (else no evictor)
      [.allowedLateness(...)]    <-  optional: "lateness" (else zero)
      [.sideOutputLateData(...)] <-  optional: "output tag" (else no side output for late data)
       .reduce/aggregate/fold/apply()      <-  required: "function"
      [.getSideOutput(...)]      <-  optional: "output tag"

Keyed Window編程結構,可以直接對輸入的stream按照Key進行操作,輸入的stream中識別Key,即輸入stream中的每個數據元素哪一部分是作爲Key來關聯這個數據元素的,這樣就可以對stream中的數據元素基於Key進行相關計算操作,如keyBy,可以根據Key進行分組(相同的Key必然可以分到同一組中去)。如果輸入的stream中沒有Key,比如就是一條日誌記錄信息,那麼無法對其進行keyBy操作。

Non-Keyed Window編程結構來說,無論輸入的stream具有何種結構(比如是否具有Key),它都認爲是無結構的,不能對其進行keyBy操作,而且如果使用Non-Keyed Window函數操作,就會對該stream進行分組(具體如何分組依賴於我們選擇的WindowAssigner,它負責將stream中的每個數據元素指派到一個或多個Window中),指派到一個或多個Window中,然後後續應用到該stream上的計算都是對Window中的這些數據元素進行操作。

從計算上看,Keyed Window編程結構會將輸入的stream轉換成Keyed stream,邏輯上會對應多個Keyed stream,每個Keyed stream會獨立進行計算,這就使得多個Task可以對Windowing操作進行並行處理,具有相同Key的數據元素會被髮到同一個Task中進行處理。而對於Non-Keyed Window編程結構,Non-Keyed stream邏輯上將不能split成多個stream,所有的Windowing操作邏輯只能在一個Task中進行處理,也就是說計算並行度爲1。

Flink支持基於時間窗口操作,也支持基於數據的窗口操作,如下圖所示:
在這裏插入圖片描述

基於時間的窗口操作,在每個相同的時間間隔對Stream中的記錄進行處理,通常各個時間間隔內的窗口操作處理的記錄數不固定;
基於數據驅動的窗口操作,可以在Stream中選擇固定數量的記錄作爲一個窗口,對該窗口中的記錄進行處理。

基於數據的窗口

  • CountWindows

基於時間的窗口:

  • Tumbling Windows,記錄沒有重疊
    在這裏插入圖片描述
    固定相同間隔分配窗口,每個窗口之間沒有重疊看圖一眼明白。

下面的例子定義了每隔3毫秒一個窗口的流:

WindowedStream<MovieRate, Integer, TimeWindow> Rates = rates
    .keyBy(MovieRate::getUserId)
    .window(TumblingEventTimeWindows.of(Time.milliseconds(3)));
  • Slide Windows,記錄有重疊
    在這裏插入圖片描述
    跟上面一樣,固定相同間隔分配窗口,只不過每個窗口之間有重疊。窗口重疊的部分如果比窗口小,窗口將會有多個重疊,即一個元素可能被分配到多個窗口裏去。

下面的例子給出窗口大小爲10毫秒,重疊爲5毫秒的流

WindowedStream<MovieRate, Integer, TimeWindow> Rates = rates
                .keyBy(MovieRate::getUserId)
                .window(SlidingEventTimeWindows.of(Time.milliseconds(10), Time.milliseconds(5)));
  • Session Windows
    在這裏插入圖片描述
    這種窗口主要是根據活動的事件進行窗口化,他們通常不重疊,也沒有一個固定的開始和結束時間。一個session window關閉通常是由於一段時間沒有收到元素。在這種用戶交互事件流中,我們首先想到的是將事件聚合到會話窗口中(一段用戶持續活躍的週期),由非活躍的間隙分隔開。

// 靜態間隔時間

WindowedStream<MovieRate, Integer, TimeWindow> Rates = rates
                .keyBy(MovieRate::getUserId)
                .window(EventTimeSessionWindows.withGap(Time.milliseconds(10)));

// 動態時間

WindowedStream<MovieRate, Integer, TimeWindow> Rates = rates
                .keyBy(MovieRate::getUserId)
                .window(EventTimeSessionWindows.withDynamicGap(()))
  • Global Windows
    在這裏插入圖片描述
    將所有數據放在一個窗口內:

     WindowedStream<MovieRate, Integer, GlobalWindow> Rates = rates
      .keyBy(MovieRate::getUserId)
      .window(GlobalWindows.create());
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章