在Flink DataStream中,可以通过Window,将无限的流(Streaming)分割成有限的批(Batch),进而进行各种统计。

本文总结Flink DataStream中Window的分类,以及Window: Tumbling Time Window(基于时间的滚动窗口)Sliding Time Window(基于时间的滑动窗口)Tumbling Count Window(基于数量的滚动窗口)Sliding Count Window(基于数量的滑动窗口)Session Window(基于会话间隔的会话窗口)Global Window(全局窗口)的使用。

Window分类

Keyed WindowNon-Keyed Window

从大的方面,Flink DataStream 中的Window可以分为Keyed WindowNon-Keyed Window

Keyed Window

stream
       .keyBy(...)                            //  Keyed Window 和 Non-Keyed Window的区别
       .window(...)                           //  必选: WindowAssigner。将接入的数据分配到不同的窗口。
      [.trigger(...)]                         //  可选: Trigger 触发器。默认为与WindowAssigner关联的默认触发器。
      [.evictor(...)]                         //  可选: Evictor 剔除器。默认无。
      [.allowedLateness(...)]                 //  可选: AllowedLateness 迟到间隔。默认0。
      [.sideOutputLateData(...)]              //  可选: 迟到数据侧输出。默认无,即默认丢掉迟到的数据。
       .reduce/process/aggregate/fold/apply() //  必选: Window Function

Non-Keyed Window

stream
       .windowAll(...)                        //  必选: WindowAssigner。将接入的数据分配到不同的窗口。
      [.trigger(...)]                         //  可选: Trigger 触发器。默认为与WindowAssigner关联的默认触发器。
      [.evictor(...)]                         //  可选: Evictor 剔除器。默认无。
      [.allowedLateness()]                    //  可选: AllowedLateness 迟到间隔。默认0。
      [.sideOutputLateData(...)]              //  可选: 迟到数据侧输出。默认无,即默认丢掉迟到的数据。
       .reduce/process/aggregate/fold/apply() //  必选: Window Function

区别与联系

  1. 关系上: Non-Keyed Window实际上是Keyed Window的一个特例。Non-Keyed Window = Keyed Window . keyBy(NullByteKeySelector),即将原始数据流按相同的Key(byte 0)分到同一个Task上。
  2. 语法上: Keyed Window需要keyBy,通过window指定窗口。Non-Keyed Window没有keyBy,通过windowAll指定窗口。
  3. 并行度: Keyed Window,基于key对原始数据流进行逻辑分组,每个逻辑Keyed Stream和其他Keyed Stream完全独立,同一个Key的数据会被分到同一个Task中,即最终窗口计算可在多个Task上并行执行。Non-Keyed Window,原始流不会被逻辑分组,并行度为1,最终只会在一个Task中执行。

Time WindowCount Window

Flink DataStream根据WindowAssigner将接入的数据分配到不同的窗口。根据WindowAssigner分配方式的不同,可将Winow分为2大类和4小类。2大类: 时间窗口(Time Window)、数量窗口(Count Window)。4小类: 全局窗口(Global Window)、滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)、会话窗口(Session Window)。

时间窗口Time Window

  1. 全局窗口(Global Window),在Keyed WindowNon-Keyed Window中均可使用。
  2. 滚动窗口(Tumbling Window),在Keyed WindowNon-Keyed Window中均可使用。
  3. 滑动窗口(Sliding Window),在Keyed WindowNon-Keyed Window中均可使用。
  4. 会话窗口(Session Window),在Keyed WindowNon-Keyed Window中均可使用。

数量窗口Count Window

  1. 全局窗口(Global Window),在Keyed WindowNon-Keyed Window中均可使用。
  2. 滚动窗口(Tumbling Window),在Keyed WindowNon-Keyed Window中均可使用。
  3. 滑动窗口(Sliding Window),在Keyed WindowNon-Keyed Window中均可使用。

以下总结几个常用Keyed Window的使用。

基于时间的滚动窗口(Tumbling Time Window)

keyedStream.window(TumblingEventTimeWindows.of(size))

# 快捷方式
keyedStream.timeWindow(Time size)

# 示例: 每隔5秒统计每个商品的点击次数
stream
    .keyBy("productID")
    .timeWindow(Time.seconds(5))
    .process(new MyCustomTimeWindowProcessFunction())
    .print();

基于时间的滑动窗口(Sliding Time Window)

keyedStream.window(SlidingEventTimeWindows.of(size, slide))

# 快捷方式
keyedStream.timeWindow(Time size, Time slide) 如:

# 示例: 每隔5秒统计最近10内每个商品的点击次数
stream
    .keyBy("productID")
    .timeWindow(Time.seconds(10),Time.seconds(5))
    .process(new MyCustomTimeWindowProcessFunction())
    .print();

基于数量的翻滚窗口(Tumbling Count Window)

keyedStream.window(GlobalWindows.create()).trigger(PurgingTrigger.of(CountTrigger.of(size)))

# 快捷方式: keyedStream.countWindow(long size) 

# 示例: 按商品分组, 每5个事件就统计一次点击的UV
stream
    .keyBy("productID")
    .countWindow(5)
    .process(new MyCustomCountWindowProcessFunction())
    .print();

基于数量的滑动窗口(Sliding Count Window)

keyedStream.window(GlobalWindows.create()).evictor(CountEvictor.of(size)).trigger(CountTrigger.of(slide))

# 快捷方式
keyedStream.countWindow(long size, long slide)

# 示例: 按商品分组, 每5个事件就统计一次最近10个事件中点击的UV
stream
    .keyBy("productID")
    .countWindow(10,5)
    .process(new MyCustomCountWindowProcessFunction())
    .print();

基于会话间隔的会话窗口(Session Window)

keyedStream.window(EventTimeSessionWindows.withGap(size))

# 示例: 统计一个用户在一个Session中浏览了多少商品
# 注意: 这里的Session Gap是5秒,含义为: 5秒钟内,改用户不活跃,则认为窗口结束,可以触发窗口计算了。
stream
    .keyBy("userID")
    .window(EventTimeSessionWindows.withGap(Time.seconds(5)))
    .process(new MyCustomSessionWindowProcessFunction())
    .print();

注意:

  1. Session Window将一段时间内的持续事件放到一个窗口。
  2. Session Window和其他的Time Window不同,Session Window没有固定的起止时间。根据Session Gap触发窗口计算。

全局窗口(Global Window)

Keyed Window中,Global Window将相同Key的数据放到同一个窗口。窗口没有明确的起止时间,需要借助于Triger来触发窗口计算。因此,使用Global Window必须指定对应的触发器。具体可参考Count Window的实现。

keyedStream.window(GlobalWindows.create()).trigger(...)

# 示例:
stream
    .keyBy("userID")
    .window(GlobalWindows.create())
    .trigger(new MyCustomGlobalWindowTriger())
    .process(new MyCustomGlobalWindowProcessFunction())
    .print()