Flink Windows窗口简介和使用

本文深入探讨了Apache Flink DataStream API中的Window概念,包括翻滚窗口和滑动窗口的使用,以及基于时间和数量的窗口操作。通过实例,详细解释了如何在无限流中设置有界集合进行操作,涵盖自定义Window机制。

Apache Flink–DataStream–Window

什么是Window?有哪些用途? 
下面我们结合一个现实的例子来说明。

我们先提出一个问题:统计经过某红绿灯的汽车数量之和? 
假设在一个红绿灯处,我们每隔15秒统计一次通过此红绿灯的汽车数量,如下图: 
这里写图片描述 
可以把汽车的经过看成一个流,无穷的流,不断有汽车经过此红绿灯,因此无法统计总共的汽车数量。但是,我们可以换一种思路,每隔15秒,我们都将与上一次的结果进行sum操作(滑动聚合),如下: 
这里写图片描述 
这个结果似乎还是无法回答我们的问题,根本原因在于流是无界的,我们不能限制流,但可以在有一个有界的范围内处理无界的流数据。

因此,我们需要换一个问题的提法:每分钟经过某红绿灯的汽车数量之和? 
这个问题,就相当于一个定义了一个Window(窗口),window的界限是1分钟,且每分钟内的数据互不干扰,因此也可以称为翻滚(不重合)窗口,如下图: 
这里写图片描述 
第一分钟的数量为8,第二分钟是22,第三分钟是27。。。这样,1个小时内会有60个window。

再考虑一种情况,每30秒统计一次过去1分钟的汽车数量之和: 
这里写图片描述 
此时,window出现了重合。这样,1个小时内会有120个window。

扩展一下,我们可以在某个地区,收集每一个红绿灯处汽车经过的数量,然后每个红绿灯处都做一次基于1分钟的window统计,即并行处理: 
这里写图片描述

通常来讲,Window就是用来对一个无限的流设置一个有限的集合,在有界的数据集上进行操作的一种机制。window又可以分为基于时间(Time-based)的window以及基于数量(Count-based)的window。

Flink DataStream API提供了Time和Count的window,同时增加了基于Session的window。同时,由于某些特殊的需要,DataStream API也提供了定制化的window操作,供用户自定义window。

下面,主要介绍Time-Based window以及Count-Based window,以及自定义的window操作,Session-Based Window操作将会在后续的文章中讲到。

1、Time-Based Window 
1.1、Tumbling window(翻滚) 
此处的window要在keyed Stream上应用window操作,当输入1个参数时,代表Tumbling window操作,每分钟统计一次,此处用scala语言实现:

 
  1. // Stream of (sensorId, carCnt)

  2. val vehicleCnts: DataStream[(Int, Int)] = ...

  3.  
  4. val tumblingCnts: DataStream[(Int, Int)] = vehicleCnts

  5. // key stream by sensorId

  6. .keyBy(0)

  7. // tumbling time window of 1 minute length

  8. .timeWindow(Time.minutes(1))

  9. // compute sum over carCnt

  10. .sum(1)

1.2、Sliding window(滑动) 
当输入2个参数时,代表滑动窗口,每隔30秒统计过去1分钟的数量:

 
  1. val slidingCnts: DataStream[(Int, Int)] = vehicleCnts

  2. .keyBy(0)

  3. // sliding time window of 1 minute length and 30 secs trigger interval

  4. .timeWindow(Time.minutes(1), Time.seconds(30))

  5. .sum(1)

Note:Flink中的Time概念共有3个,即Processing Time(wall clock),Event Time以及Ingestion Time,我会在后续的文章中讲到。(国内目前wuchong以及Vinoyang都有讲过).

2、Count-Based Window 
2.1、Tumbling Window 
和Time-Based一样,Count-based window同样支持翻滚与滑动窗口,即在Keyed Stream上,统计每100个元素的数量之和:

 
  1. // Stream of (sensorId, carCnt)

  2. val vehicleCnts: DataStream[(Int, Int)] = ...

  3.  
  4. val tumblingCnts: DataStream[(Int, Int)] = vehicleCnts

  5. // key stream by sensorId

  6. .keyBy(0)

  7. // tumbling count window of 100 elements size

  8. .countWindow(100)

  9. // compute the carCnt sum

  10. .sum(1)

2.2、Sliding Window 
每10个元素统计过去100个元素的数量之和:

 
  1. val slidingCnts: DataStream[(Int, Int)] = vehicleCnts

  2. .keyBy(0)

  3. // sliding count window of 100 elements size and 10 elements trigger interval

  4. .countWindow(100, 10)

  5. .sum(1)

3、Advanced Window(自定义window) 
自定义的Window需要指定3个function。 
3.1、Window Assigner:负责将元素分配到不同的window。

 
  1. // create windowed stream using a WindowAssigner

  2. var windowed: WindowedStream[IN, KEY, WINDOW] = keyed

  3. .window(myAssigner: WindowAssigner[IN, WINDOW])

WindowAPI提供了自定义的WindowAssigner接口,我们可以实现WindowAssigner的public abstract Collection<W> assignWindows(T element, long timestamp)方法。同时,对于基于Count的window而言,默认采用了GlobalWindow的window assigner,例如:

keyValue.window(GlobalWindows.create())

3.2、Trigger 
Trigger即触发器,定义何时或什么情况下Fire一个window。 
我们可以复写一个trigger方法来替换WindowAssigner中的trigger,例如:

 
  1. // override the default trigger of the WindowAssigner

  2. windowed = windowed

  3. .trigger(myTrigger: Trigger[IN, WINDOW])

对于CountWindow,我们可以直接使用已经定义好的Trigger:CountTrigger

trigger(CountTrigger.of(2))

3.3、Evictor(可选) 
驱逐者,即保留上一window留下的某些元素。

 
  1. // specify an optional evictor

  2. windowed = windowed

  3. .evictor(myEvictor: Evictor[IN, WINDOW])

Note:最简单的情况,如果业务不是特别复杂,仅仅是基于Time和Count,我们其实可以用系统定义好的WindowAssigner以及Trigger和Evictor来实现不同的组合: 
例如:基于Event Time,每5秒内的数据为界,以每秒的滑动窗口速度进行operator操作,但是,当且仅当5秒内的元素数达到100时,才触发窗口,触发时保留上个窗口的10个元素。

 
  1. keyedStream

  2. .window(SlidingEventTimeWindows.of(Time.seconds(5), Time.seconds(1))

  3. .trigger(CountTrigger.of(100))

  4. .evictor(CountEvictor.of(10));

 
  1. val countWindowWithoutPurge = keyValue.window(GlobalWindows.create()).

  2. trigger(CountTrigger.of(2))

 

最后,给出一个完整的例子,说明Window的用法:

 
  1. import org.apache.flink.streaming.api.scala._

  2. import org.apache.flink.streaming.api.windowing.assigners.GlobalWindows

  3. import org.apache.flink.streaming.api.windowing.triggers.{CountTrigger, PurgingTrigger}

  4. import org.apache.flink.streaming.api.windowing.windows.GlobalWindow

  5.  
  6. object Window {

  7. def main(args: Array[String]) {

  8.  
  9. // set up the execution environment

  10. val env = StreamExecutionEnvironment.getExecutionEnvironment

  11.  
  12. val source = env.socketTextStream("localhost",9000)

  13.  
  14. val values = source.flatMap(value => value.split("\\s+")).map(value => (value,1))

  15.  
  16. val keyValue = values.keyBy(0)

  17.  
  18. // define the count window without purge

  19.  
  20. val countWindowWithoutPurge = keyValue.window(GlobalWindows.create()).

  21. trigger(CountTrigger.of(2))

  22.  
  23.  
  24. val countWindowWithPurge = keyValue.window(GlobalWindows.create()).

  25. trigger(PurgingTrigger.of(CountTrigger.of[GlobalWindow](2)))

  26.  
  27. countWindowWithoutPurge.sum(1).print()

  28.  
  29. countWindowWithPurge.sum(1).print()

  30.  
  31. env.execute()

  32.  
  33. // execute program

  34. env.execute("Flink Scala API Skeleton")

  35. }

  36. }

1]: http://flink.apache.org/news/2015/12/04/Introducing-windows.html

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值