数据分析实践 | flink | window窗口篇

要弄懂flink全部的工作机制其实是一件比较麻烦的事情,零碎的概念太多,对于第一次接触flink的我也算是个挑战。

整体感受就是--看官方文档的过程就像在做高中的阅读理解:)
如果你刚接触,看官方文档又很晕,那请看下去咯,我会配合它工作流程的顺序一一介绍概念。

接触不久,文章如有错误还请大牛多指点。


flink流计算--window窗口

window是处理数据的核心。按需选择你需要的窗口类型后,它会将传入的原始数据流切分成多个buckets,所有计算都在window中进行。

这里按照数据处理前、中、后为过程来描述一个窗口的工作过程。

0x01数据处理前的分流

窗口在处理数据前,会对数据做分流,有两种控制流的方式:

* Keyed Windows
<可以理解为�按照原始数据流中的某个key进行分类,拥有同一个key值的数据流将为进入同一个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
<不做分类,每进入一条数据即增加一个窗口,多个窗口并行,每个窗口处理1条数据>    

        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"

用`[ ]`包含的内容,其中的方法均为可选函数,如需了解可以查阅一下官方文档。

0x02窗口函数的准备

对于每个window必备的是触发器Trigger和一个附加在window上的函数
ProcessWindowFunction
ReduceFunction
AggregateFunction
FoldFunction
用于实现window中对数据流的操作。
就是上面分流示例中的.reduce/aggregate/fold/apply()中需要做的操作。

在对数据流做处理前,需要先预设一些窗口的配置,先看一下窗口的一些类型:

2.1窗口选择

划分

* time  - 根据时间划分窗口
    #时间类型:
    * EventTime 数据本身携带的时间
    * ProcessingTime 处理时间
    
* count - 根据数据量划分窗口

属性

* size=interval 无重叠数据,可理解为翻滚窗口,
* size>interval 有重叠数据,可理解为滑动窗口

手画了一张图表示这两个窗口的区别:
滑动窗口(10s,5s)的意思是时间窗口长度为10s,滑动长度为5s。
于是一共有这几个窗口类型

* 无重叠时间窗口
* 有重叠时间窗口
* 有重叠数据窗口
* 无重叠数据窗口

实际场景中用的较多的还是时间窗口,以时间窗口为例。

2.2时间窗口

声明使用的窗口时间类型:

Scala:
import org.apache.flink.streaming.api.TimeCharacteristic
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic ( TimeCharacteristic.EventTime )
//设置窗口时间类型,如若此处为ProcessingTime则无需做后续的指定时间操作,其他两种时间均需要指定数据流中的时间参数。

选择完时间类型之后,我们优先挑选最复杂的一种情况来说明时间戳和水位线的工作机制,如果选择了EventTime,需要指定数据流中的时间戳。

val Stream :DataStream[String]=env.addSource(kafkaTableSoure)
val frequency_ = Stream.map({...})//此处略过数据的预处理
val frequency = frequency_.assignTimestampsAndWatermarks(new TimestampExtractor())//指定时间戳与水位线

指定时间戳和配置水位线就由这个函数TimestampExtractor()继承AssignerWithPeriodicWatermarks方法来配置

class TimestampExtractor extends AssignerWithPeriodicWatermarks[Map[String,Any]] with Serializable {

  var currentMaxTimestamp: Long = _
  val maxOutOfOrderness = 10000L//最大允许的乱序时间是10s

  var a : Watermark = null
  val format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  //extractTimestamp指定毫秒级时间戳,必须是毫秒级!!!
  override def extractTimestamp(t: Map[String,Any], l: Long): Long = {
    val timestamp = t.get("time_local").get.toString.toLong*1000L 
    //此处是因为时间戳没有毫秒级,故*1000变成毫秒级时间戳
    currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp)
    println("timestamp:" + timestamp+","+format.format(timestamp) +"|"+ currentMaxTimestamp + ","+ format.format(currentMaxTimestamp) + ","+ a.toString)
    timestamp
  }
  //getCurrentWatermark设置水位线
  override def getCurrentWatermark: Watermark = {
    a = new Watermark(currentMaxTimestamp - maxOutOfOrderness)
    a
  }
}

这个class中有两个方法,分别配置时间戳和水位线,关于这两个概念需要介绍一下:

2.2.1 分配时间戳

为了处理事件时间,Flink需要知道事件的时间戳,这意味着流中的每条数据都需要分配其事件时间戳。

这通常通过提取每条数据中的固定字段来完成时间戳的获取。

2.2.2 水印

这段代码中其实没有体现出水印的概念,但水印与时间戳、watermark都有密切关系。

时间戳分配与生成水印密切相关,水印告诉系统事件时间的进展。决定水位线的高度。

有两种方式可以分配时间戳并生成水印:
1.直接在数据流源中
    通过时间戳分配器/水印生成器:在Flink中,时间戳分配器定义要发出的水印。
    ex:
    参考上方示例代码中的`TimestampExtractor()`类
    
2.Kafka读取时间戳
    当使用Apache Kafka作为数据源时,每个Kafka分区可能具有简单的事件时间模式(升序时间戳或有界无序)。
    在这种情况下,可以使用Flink的Kafka分区感知水印生成,通常Kafka是多个分区并行读取,每个Kafka分区在Kafka使用者内部生成水印。
    如果严格按照Kafka分区升序,则使用升序时间戳水印生成器生成每分区水印将产生完美的整体水印。
    ex:
    val kafkaSource = new FlinkKafkaConsumer09[MyType]("myTopic", schema, props)
    kafkaSource.assignTimestampsAndWatermarks(new AscendingTimestampExtractor[MyType] {
        def extractAscendingTimestamp(element: MyType): Long = element.eventTimestamp
    })
    val stream: DataStream[MyType] = env.addSource(kafkaSource)
2.2.3 水位线Watermark

通常在处理EventTime事件时间的时候使用,流式传输程序需要相应地设置时间特性。
数据流的到达顺序我们无法保证的情况下,需要对迟到的数据进行处理,Periodic水位线便是配置这个特性。
AssignerWithPeriodicWatermarks分配时间戳并定期生成水印

//getCurrentWatermark设置水位线
  override def getCurrentWatermark: Watermark = {
    a = new Watermark(currentMaxTimestamp - maxOutOfOrderness)
    a
  }

代码中currentMaxTimestamp为当前的事件时间,a 则是watermark的值。
需要我们设置的是maxOutOfOrderness这个差值,来确认数据最大可以迟到多久。

图片来自:https://blog.csdn.net/lmalds/article/details/52704170 文章对水位线描述的很详细。

这里借这张图来说明一下乱序数据流到达时的工作过程:

预设值:
* window大小是3秒,window被分为如下的形式,
[19:34:21,19:34:24)
[19:34:24,19:34:27)
...
* 水位线设置差值为10s

1.EventTime为19:34:22的数据到达,创建第一个窗口。
此时水位线为19:34:22-10s=19:34:12,此条数据属于[19:34:21,19:34:24)窗口范围内。

2.EventTime为19:34:26的数据到达,创建第二个窗口。
此时水位线为19:34:26-10s=19:34:16,此条数据属于[19:34:24,19:34:27)窗口范围内

3.EventTime为19:34:32的数据到达,进入第一个窗口。
此时水位线为19:34:32-10s=19:34:22,因为设置最大可延迟10s到达 ,所以这条数据属于[19:34:21,19:34:24)窗口范围内。

4.EventTime为19:34:33的数据到达,进入第一个窗口
此时水位线为19:34:33-10s=19:34:23,因为设置最大可延迟10s到达 ,所以此条数据属于[19:34:21,19:34:24)窗口范围内

5.EventTime为19:34:34的数据到达,进入第二个窗口
此时水位线为19:34:34-10s=19:34:24,是[19:34:21,19:34:24)窗口的临界值,
触发执行窗口函数,统计第一个窗口中的数据。

6.EventTime为19:34:36的数据到达,进入第二个窗口
此时水位线为19:34:36-10s=19:34:26,属于[19:34:24,19:34:27)窗口范围内
...

2.3窗口函数

窗口函数是触发器在确认窗口数据到达完毕后,执行的函数。

flink提供了两类窗口函数,

  • AggerateFunction/ReduceFunction/FoldFunction/...

此类为数据计算函数,适用于仅计算,无需做时间窗口的情况。
AggerateFunction为用户自定义函数,可以按照个人需求做各类统计。

  • WindowFunction/ProcessWindowFunction/...

此类为做窗口函数,适用于无需计算只做时间窗口统计的情况。(ps.貌似很少有这样的情况猴)

计算函数+窗口函数

两种方式的结合适用于需要进行计算后再做滑动窗口统计结果的情况。
(直接对全部SourceData数据做WindowFunction消耗会较大,所以先做计算,提取出需要的特征、结果后,减轻窗口函数的压力。)

窗口函数计算完毕后,就能够得到计算结果了,整个流程便算是结束了。

0x03 TODO

概念差不多都解释清楚啦。
在下篇中将详细讲概念实践,也会细说一下AggerateFunction自定义函数的使用。
待更。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容