👉引言💎
学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。 热爱写作,愿意让自己成为更好的人............
铭记于心 | ||
🎉✨🎉我唯一知道的,便是我一无所知🎉✨🎉 |
三、Window
1 概述:
- 内容涵盖:
- window的基本概念、分类、以及三种最常见的window的功能;同时也会讲解使用window的时候的一些典型的问题;
- window中涉及到的一些高级的优化及其实现原理
- Window分类:典型Window:
- Tumble Window(滚动)
- Sliding Window(滑动)
- Session Window(会话)
- 其他:
- 全局Window
- Count Window
- 累计窗口
- 使用:
2 TUMBLE Window (滚动窗口)
这是最常见的窗口类型,就是根据数据的时间(可以是处理时间,也可以是事件时间)划分到它所属的窗口中windowStart = timestamp - timestamp % windowSize
,这条数据所属的window就是[windowStart, windowStart + windowSize)
在我们使用window的过程中,最容易产生的一个疑问是,window的划分是subtask级别的,还是key级别的。这里大家要记住,Flink 中的窗口划分是key级别的。 比如下方的图中,有三个key,那每个key的窗口都是单独的。所以整个图中,一种存在14个窗口。
窗口的触发,是时间大于等于window end的时候,触发对应的window的输出(计算有可能提前就增量计算好了),目前的实现是给每个window都注册一个timer,通过处理时间或者事件时间的timer来触发window的输出。
3 HOP Window (滑动窗口)
了解了上面的TUMBLE窗口的基本原理后,HOP窗口就容易理解了。上面的TUMBLE窗口是每条数据只会落在一个窗口中。在HOP窗口中,每条数据是可能会属于多个窗口的(具体属于多少,取决于窗口定义的大小和滑动),比如下图中假设滑动是1h的话,那窗口大小就是2h,这种情况每条数据会属于两个窗口。除了这一点之外,其它的基本跟HOP窗口是类似的,比如也是key级别划分窗口,也是靠timer进行窗口触发输出。
4 Session Window (会话窗口)
会话窗口跟上面两种窗口区别比较大,上面两个窗口的划分,都是根据当前数据的时间就可以直接确定它所属的窗口。会话窗口的话,是一个动态merge的过程。一般会设置一个会话的最大的gap,比如10min。
那某个key下面来第一条数据的时候,它的window就是 [event_time, event_time + gap),当这个key后面来了另一条数据的时候,它会立即产生一个窗口,如果这个窗口跟之前的窗口有overlap的话,则会将两个窗口进行一个merge,变成一个更大的窗口,此时需要将之前定义的timer取消,再注册一个新的timer。
所以会话窗口要求所有的聚合函数都必须有实现merge
5 迟到数据处理
- 定义迟到
watermark驱动某个窗口触发输出后,这个窗口如果后面又来了数据,则为迟到数据(注意,不是数据的时间晚于watermark就算是迟到,而是它所属的窗口已经被触发了,才算迟到)
即: 一条数据到来后,会用WindowAssigner给它划分一个window,一般时间窗口是一个时间区间,比如[10:00, 11:00),如果划分出来的window end 比当前的watermark值还小,说明这个窗口已经触发了计算了,这条数据会被认为是迟到数据。 - 只有事件时间 下才会有迟到的数据
- 对于迟到的数据,我们现在有两种处理方式:
- Allow lateness
这种方式需要设置一个允许迟到的时间,设置之后,窗口正常计算结束后,不会马上清理状态,而是会多保留allowLateness这么长时间,在这段时间内如果还有数据到来,则继续之前的状态进行计算。 - SideOutput
这种方式需要对迟到数据打一个tag,然后在DataStream上根据这个tag获取到迟到数据流,然后业务层面自行选择进行处理
- 注意:side output只有在DataStream的窗口中才可以用,在SQL中目前还没有这种语义,所以暂时只有drop这一个策略。
6 增量计算 VS 全量计算
这个问题也是使用窗口的时候最典型的问题之一。先定义一下:
- 增量计算:每条数据到来后,直接参与计算(但是还不需要输出结果)
- 全量计算:每条数据到来后,先放到一个buffer中,这个buffer会存储到状态里,直到窗口触发输出的时候,才把所有数据拿出来统一进行计算
在SQL里面,主要是窗口聚合,所以都是可以增量计算的,也就是每条数据来了之后都可以直接进行计算,而不用把数据都存储起来。举个例子,比如要做sum
计算,那每来一条数据,就直接把新的数据加到之前的sum
值上即可,这样我们就只需要存储一个sum
值的状态,而不需要存储所有buffer的数据,状态量会小很多。
DataStream里面要用增量计算的话,需要用reduce
/aggregate
等方法,就可以用到增量计算。如果用的是process
接口,这种就属于是全量计算。
7 EMIT触发
上面讲到,正常的窗口都是窗口结束的时候才会进行输出,比如一个1天的窗口,只有到每天结束的时候,窗口的结果才会输出。这种情况下就失去了实时计算的意义了.
那么EMIT触发就是在这种情况下,可以提前把窗口内容输出出来的一种机制。比如我们可以配置一个1天的窗口,每隔5s输出一次它的最新结果,那这样下游就可以更快的获取到窗口计算的结果了.
这个功能只在SQL中,如果是在DataStream中需要完成类似的功能,需要自己定义一些trigger来做.
上节课中,有讲到retract机制,这里需要提一下,这种emit的场景就是一个典型的retract的场景,发送的结果类似于+[1], -[1], +[2], -[2], +[4]这样子。这样才能保证window的输出的最终结果是符合语义的。
8 Window Offset
按照上面提到的,滚动窗口的计算方式是:windowStart = timestamp - timestamp % windowSize
[windowStart, windowStart + windowSize),这个时间戳是按照unix timestamp来算的。比如我们要用一个一周的窗口,想要的是从周一开始,到周日结束,但是按照上面这种方式计算出来的窗口的话,就是从周四开始的(因为1970年1月1日是周四)。
那么window offset的功能就是可以在计算窗口的时候,可以让窗口有一个偏移。所以最终计算window的公式就变成了:windowStart = timestamp - (timestamp - offset + windowSize) % windowSize
DataStream原生就是支持offset的,但是SQL里并不支持,字节内部版本扩展支持了SQL的window offset功能
9 总结
- 三种(滚动、滑动、会话)窗口的定义
- 迟到数据处理: AllowLateness, SideOutput
- 增量计算和全量计算模型
- EMIT触发提前输出窗口的结果
🌹写在最后💖: 路漫漫其修远兮,吾将上下而求索!伙伴们,再见!🌹🌹🌹