Flink的window机制(一)

版权声明:原创文章,转载请注明出处 https://blog.csdn.net/xianpanjia4616/article/details/85010182

Flink中的window操作是非常丰富的,不得不承认这一点要比spark做的好很多,由于Flink的window机制是比较复杂的,也比较的难理解,所以我会分几篇去介绍他,下面我们一起揭开它神秘的面纱,听我细细道来.

什么是Windows?

窗口是无限流处理的核心部分,窗口把无限的流分成了有限大小的桶,我们可以在桶上面进行计算,这篇文章会教大家在Flink中如何进行窗口操作,以及开发者如何从Flink提供的窗口函数中获得最大的收益.

为什么需要Window呢?

在一些流的应用中,数据都是连续性的,因此我们不能等待所有数据都流进来才开始处理,当然了,我们可以只要数据一进来就立马开始处理,然后等下一个数据,但是呢,在另外一些需求中,我们可能需要做一些聚合对于到来的数据,比如说,在过去的10分钟内,用户点击了多少次页面的链接,在这种例子中呢,我们就必须定义一个窗口,然后在这个窗口内对数据进行计算.

在Flink的窗口化程序中一般的结构如下,分组的流和非分组的流,他们唯一的区别就是分组流调用了keyBy(…)和window(…),而非分组的流window()换成了windowAll(…).

Keyed Windows

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"

在上面的例子中,方括号[]内的命令是可选的,这表明Flink允许你根据最符合你的要求来定义自己的窗口逻辑。

Window 的生命周期

简而言之,当一个属于窗口的元素到达之后这个窗口就创建了,而当当前时间(事件或者处理时间)为窗口的创建时间跟用户指定的延迟时间相加时,窗口将被彻底清除。Flink 确保了只清除基于时间的窗口,其他类型的窗口不清除,例如:全局窗口.例如:对于一个每5分钟创建无覆盖的(即 翻滚窗口)窗口,允许一个1分钟的时延的窗口策略,Flink将会在12:00到12:05这段时间内第一个元素到达时创建窗口,当水印通过12:06时,移除这个窗口.

分组和非分组Windows (Keyed vs Non-Keyed Windows)

首先,第一件事是指定你的数据流是分组的还是未分组的,这个必须在定义 window 之前指定好。使用 keyBy(...) 会将你的无限数据流拆分成逻辑分组的数据流,如果 keyBy(...) 函数不被调用的话,你的数据流将不是分组的。

窗口分配器(Window Assingers)

指定完你的数据流是分组的还是非分组的之后,接下来你需要定义一个窗口分配器(window assigner),窗口分配器定义了元素如何分配到窗口中,这是通过在分组数据流中调用window(...)或者非分组数据流中调用windowAll(...)时你选择的窗口分配器(WindowAssigner)来指定的。WindowAssigner是负责将每一个到来的元素分配给一个或者多个窗口(window),Flink 提供了一些常用的预定义窗口分配器,即:滚动窗口、滑动窗口、会话窗口和全局窗口。你也可以通过继承WindowAssigner类来自定义自己的窗口。所有的内置窗口分配器(除了全局窗口 global window)都是通过时间来分配元素到窗口中的,这个时间要么是处理的时间,要么是事件发生的时间。

窗口分类(是否重叠)

Tumbling Window(滚动窗口,即没有重叠)

滚动窗口,在流数据上面滚动,这种类型的窗口是没有重叠的,在一个窗口里面的事件或者数据,是不会出现在另一个窗口的,即不会发生重叠.例如:如果你指定了一个5分钟大小的滚动窗口,当前窗口将被评估并将按下图说明每5分钟创建一个新的窗口.看下面的图片有助于我们的理解.

从这个图中我们可以看到window1,window2,到window5之间都没有重叠,这就是一个滚动的窗口,他有一个固定的大小.例如:如果你指定了一个5分钟大小的滚动窗口,当前窗口将被计算并将按上图说明每5分钟创建一个新的窗口

Sliding Window(滑动窗口,有重叠)

滑动窗口,跟滚动窗口刚好相反,从流数据上面滑过,正因为如此,滑动窗口是有重叠的,它对传入的数据流提供了更平滑的聚合,由于你不是从一组输入跳转到下一组输入,而是在传入的数据流上滑动。看下面的图片.

从上面的图可以看出这种窗口是有重叠的,window2的数据出现了window1里面,这个就是滑动窗口.例如,你有10分钟的窗口和5分钟的滑动,那么每个窗口中5分钟的窗口里包含着上个10分钟产生的数据.

会话窗口(Session Windows)

session窗口分配器通过session活动来对元素进行分组,session窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况。相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个session窗口通过一个session间隔来配置,这个session间隔定义了非活跃周期的长度。当这个非活跃周期产生,那么当前的session将关闭并且后续的元素将被分配到新的session窗口中去。看下面的图片.

可以看出会话窗口的特点是没有重叠,也没有固定的时间,他是在没有数据进来的时候关闭的.

全局窗口(Global Windows)

全局窗口分配器将所有具有相同key的元素分配到同一个全局窗口中,这个窗口模式仅适用于用户还需自定义触发器的情况。否则,由于全局窗口没有一个自然的结尾,无法执行元素的聚合,将不会有计算被执行。看下面的图片.

可以看出全局窗口的特点没有时间限制,他是把相同的key分配的一个窗口中,但是他必须自定义一个触发器,不然就没办法计算,因为他不知道什么时候开始计算.

window操作大致就分为这几种,下面看一下每一种窗口的wordcount的demo:

1.基于时间的

1.1、Tumbling window(滚动) 

 val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.socketTextStream("192.168.17.142", 9999)
    val counts = text.flatMap(_.toLowerCase.split(" ")
      .filter(_.nonEmpty))
      .map((_, 1))
      .keyBy(0)
      .timeWindow(Time.minutes(1))
      .sum(1)
    counts.print()
    env.execute("window")

1.2、Sliding window(滑动) 

val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.socketTextStream("192.168.17.142", 9999)
    val counts = text.flatMap(_.toLowerCase.split(" ")
      .filter(_.nonEmpty))
      .map((_, 1))
      .keyBy(0)
      .timeWindow(Time.minutes(1),Time.seconds(30))
      .sum(1)
    counts.print()
    env.execute("window")

 2.基于Count的

2.1、Tumbling window(滚动)

  val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.socketTextStream("192.168.17.142", 9999)
    val counts = text.flatMap(_.toLowerCase.split(" ")
      .filter(_.nonEmpty))
      .map((_, 1))
      .keyBy(0)
      .countWindow(10)
      .sum(1)
    counts.print()
    env.execute("window")

 2.2、Sliding window(滑动)

 val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.socketTextStream("192.168.17.142", 9999)
    val counts = text.flatMap(_.toLowerCase.split(" ")
      .filter(_.nonEmpty))
      .map((_, 1))
      .keyBy(0)
      .countWindow(100,10)
      .sum(1)
    counts.print()
    env.execute("window")

 Session Window

 val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.socketTextStream("192.168.17.142", 9999)
    val counts = text.flatMap(_.toLowerCase.split(" ")
      .filter(_.nonEmpty))
      .map((_, 1))
      .keyBy(0)
      .window(ProcessingTimeSessionWindows.withGap(Time.seconds(30)))
      .sum(1)
    counts.print()
    env.execute("window")

今天先写到这里,后续接着介绍.Triggers and Evictors会在下一篇中讲解.

如果有写的不对的地方,欢迎大家指正,如果有什么疑问,可以加QQ群:340297350,谢谢

猜你喜欢

转载自blog.csdn.net/xianpanjia4616/article/details/85010182