Flink DataStream API (二) Time 與Window

一、Time

在 Flink 的流式處理中,會涉及到時間的不同概念,如下圖所示:

Event Time:是事件創建的時間。它通常由事件中的時間戳描述, 例如採集的日誌數據中,每一條日誌都會記錄自己的生成時間, Flink 通過時間戳分配器訪問事件時間戳。

Ingestion Time:是數據進入 Flink 的時間。

Processing Time:是每一個執行基於時間操作的算子的本地系統時間,與機器相關,默認的時間屬性就是 Processing Time。

例如,一條日誌進入 Flink 的時間爲 2017-11-12 10:00:00.123,到達 Window 的系統時間爲 2017-11-12 10:00:01.234,日誌的內容如下:

2017-11-02 18:37:15.624 INFO Fail over to rm2

對於業務來說,要統計 1min 內的故障日誌個數,哪個時間是最有意義的? ——eventTime,因爲我們要根據日誌的生成時間進行統計。

二、Window

1 、Window 概述

streaming 流式計算是一種被設計用於處理無限數據集的數據處理引擎,而無限數據集是指一種不斷增長的本質上無限的數據集,而 window 是一種切割無限數據爲有限塊進行處理的手段。

Window 是無限數據流處理的核心, Window 將一個無限的 stream 拆分成有限大小的” buckets”桶,我們可以在這些桶上做計算操作。

2 Window 類型

Window 可以分成兩類:

CountWindow: 按照指定的數據條數生成一個 Window,與時間無關。

TimeWindow:按照時間生成 Window。

對於 TimeWindow,可以根據窗口實現原理的不同分成三類: 滾動窗口(TumblingWindow) 、 滑動窗口(Sliding Window) 和會話窗口(Session Window) 。

1. 滾動窗口(Tumbling Windows)

將數據依據固定的窗口長度對數據進行切片。

特點: 時間對齊,窗口長度固定,沒有重疊。

適用場景:適合做 BI 統計等(做每個時間段的聚合計算)。

滾動窗口分配器將每個元素分配到一個指定窗口大小的窗口中,滾動窗口有一個固定的大小,並且不會出現重疊。例如: 如果你指定了一個 5 分鐘大小的滾動窗口, 窗口的創建如下圖所示:
 

2. 滑動窗口(Sliding Windows)

滑動窗口是固定窗口的更廣義的一種形式,滑動窗口由固定的窗口長度和滑動間隔組成。

特點: 時間對齊,窗口長度固定,有重疊。

適用場景:對最近一個時間段內的統計(求某接口最近 5min 的失敗率來決定是否要報警)。

滑動窗口分配器將元素分配到固定長度的窗口中,與滾動窗口類似,窗口的大小由窗口大小參數來配置,另一個窗口滑動參數控制滑動窗口開始的頻率。因此,滑動窗口如果滑動參數小於窗口大小的話,窗口是可以重疊的,在這種情況下元素會被分配到多個窗口中。

例如,你有 10 分鐘的窗口和 5 分鐘的滑動,那麼每個窗口中 5 分鐘的窗口裏包含着上個 10 分鐘產生的數據,如下圖所示:

3. 會話窗口(Session Windows)

由一系列事件組合一個指定時間長度的 timeout 間隙組成,類似於 web 應用的session,也就是一段時間沒有接收到新數據就會生成新的窗口。

特點: 時間無對齊。

session 窗口分配器通過 session 活動來對元素進行分組, session 窗口跟滾動窗口和滑動窗口相比,不會有重疊和固定的開始時間和結束時間的情況,相反, 當它在一個固定的時間週期內不再收到元素,即非活動間隔產生,那個這個窗口就會關
閉。一個 session 窗口通過一個 session 間隔來配置,這個 session 間隔定義了非活躍週期的長度,當這個非活躍週期產生,那麼當前的 session 將關閉並且後續的元素將被分配到新的 session 窗口中去

三、Window API

6.3.1 CountWindow

CountWindow 根據窗口中相同 key 元素的數量來觸發執行,執行時只計算元素數量達到窗口大小的 key 對應的結果。

注意: CountWindow 的 window_size 指的是相同 Key 的元素的個數,不是輸入的所有元素的總數。

1 、滾動窗口

默認的 CountWindow 是一個滾動窗口,只需要指定窗口大小即可,當元素數量達到窗口大小時,就會觸發窗口的執行。

// 獲取執行環境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 創建 SocketSource
val stream = env.socketTextStream("localhost", 11111)
// 對 stream 進行處理並按 key 聚合
val streamKeyBy = stream.map(item => (item.split(" ")(0), item.split("
")(1).toLong)).keyBy(0)
// 引入滾動窗口
// 這裏的 5 指的是 5 個相同 key 的元素計算一次
val streamWindow = streamKeyBy.countWindow(5)
// 執行聚合操作
val streamReduce = streamWindow.reduce(
(item1, item2) => (item1._1, item1._2 + item2._2)
)
// 將聚合數據寫入文件
streamReduce.print()
// 執行程序
env.execute("TumblingWindow")

2 滑動窗口

滑動窗口和滾動窗口的函數名是完全一致的,只是在傳參數時需要傳入兩個參數,一個是 window_size,一個是 sliding_size。

下面代碼中的 sliding_size 設置爲了 2,也就是說,每收到兩個相同 key 的數據就計算一次,每一次計算的 window 範圍是 5 個元素。
 

    // 獲取執行環境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 創建 SocketSource
    val stream = env.socketTextStream("localhost", 11111)
    // 對 stream 進行處理並按 key 聚合
    val streamKeyBy = stream.map(item => (item.split(" ")(0), item.split("
    ")(1).toLong)).keyBy(0)
    // 引入滾動窗口
    // 當相同 key 的元素個數達到 2 個時,觸發窗口計算,計算的窗口範圍爲 5
    val streamWindow = streamKeyBy.countWindow(5,2)
    // 執行聚合操作
    val streamReduce = streamWindow.reduce(
      (item1, item2) => (item1._1, item1._2 + item2._2)
    )
    // 將聚合數據寫入文件
    streamReduce.print()
    // 執行程序
    env.execute("TumblingWindow")
  }

6.3.2 TimeWindow

TimeWindow 是將指定時間範圍內的所有數據組成一個 window,一次對一個window 裏面的所有數據進行計算。

1. 滾動窗口

Flink 默認的時間窗口根據 Processing Time 進行窗口的劃分,將 Flink 獲取到的數據根據進入 Flink 的時間劃分到不同的窗口中。
 

// 獲取執行環境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 創建 SocketSource
val stream = env.socketTextStream("localhost", 11111)
// 對 stream 進行處理並按 key 聚合
val streamKeyBy = stream.map(item => (item, 1)).keyBy(0)
// 引入時間窗口
val streamWindow = streamKeyBy.timeWindow(Time.seconds(5))
// 執行聚合操作
val streamReduce = streamWindow.reduce(
(item1, item2) => (item1._1, item1._2 + item2._2)
)
// 將聚合數據寫入文件
streamReduce.print()
// 執行程序
env.execute("TumblingWindow")

時間間隔可以通過 Time.milliseconds(x), Time.seconds(x), Time.minutes(x)等其中的一個來指定。

2. 滑動窗口(SlidingEventTimeWindows)

滑動窗口和滾動窗口的函數名是完全一致的,只是在傳參數時需要傳入兩個參數,一個是 window_size,一個是 sliding_size。

下面代碼中的 sliding_size 設置爲了 2s,也就是說,窗口每 2s 就計算一次,每一次計算的 window 範圍是 5s 內的所有元素。

// 獲取執行環境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 創建 SocketSource
val stream = env.socketTextStream("localhost", 11111)
// 對 stream 進行處理並按 key 聚合
val streamKeyBy = stream.map(item => (item, 1)).keyBy(0)
// 引入滾動窗口
val streamWindow = streamKeyBy.timeWindow(Time.seconds(5), Time.seconds(2))
// 執行聚合操作
val streamReduce = streamWindow.reduce(
(item1, item2) => (item1._1, item1._2 + item2._2)
)
// 將聚合數據寫入文件
streamReduce.print()
// 執行程序
env.execute("TumblingWindow")

6.3.3 Window Reduce

WindowedStream → DataStream:給 window 賦一個 reduce 功能的函數,並返回一個聚合的結果。
 

// 獲取執行環境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 創建 SocketSource
val stream = env.socketTextStream("localhost", 11111)
// 對 stream 進行處理並按 key 聚合
val streamKeyBy = stream.map(item => (item, 1)).keyBy(0)
// 引入時間窗口
val streamWindow = streamKeyBy.timeWindow(Time.seconds(5))
// 執行聚合操作
val streamReduce = streamWindow.reduce(
(item1, item2) => (item1._1, item1._2 + item2._2)
)
// 將聚合數據寫入文件
streamReduce.print()
// 執行程序
env.execute("TumblingWindow")

6.3.4 Window Fold

WindowedStream → DataStream:給窗口賦一個 fold 功能的函數,並返回一個 fold 後的結果。
 

// 獲取執行環境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 創建 SocketSource
val stream = env.socketTextStream("localhost", 11111,'\n',3)
// 對 stream 進行處理並按 key 聚合
val streamKeyBy = stream.map(item => (item, 1)).keyBy(0)
// 引入滾動窗口
val streamWindow = streamKeyBy.timeWindow(Time.seconds(5))
// 執行 fold 操作
val streamFold = streamWindow.fold(100){
(begin, item) =>
begin + item._2
}
// 將聚合數據寫入文件
streamFold.print()
// 執行程序
env.execute("TumblingWindow")

6.3.5 Aggregation on Window

WindowedStream → DataStream:對一個 window 內的所有元素做聚合操作。min 和 minBy 的區別是 min 返回的是最小值,而 minBy 返回的是包含最小值字段的元素(同樣的原理適用於 max 和 maxBy)。

// 獲取執行環境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 創建 SocketSource
val stream = env.socketTextStream("localhost", 11111)
// 對 stream 進行處理並按 key 聚合
val streamKeyBy = stream.map(item => (item.split(" ")(0), item.split(" ")(1))).keyBy(0)
// 引入滾動窗口
val streamWindow = streamKeyBy.timeWindow(Time.seconds(5))
// 執行聚合操作
val streamMax = streamWindow.max(1)
// 將聚合數據寫入文件
streamMax.print()
// 執行程序
env.execute("TumblingWindow")

 

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