这是我参与「第四届青训营 」笔记创作活动的第六天,今日对于《流式计算中的 Window 计算》进行了加深,了解了几种常见的window,学习了watermark,根据课堂和预习资料,以及自己的理解,做了以下简单的整理:
Watermark
这部分会对 Watermark 的概念、产生、传递以及典型的一些生产实践中的遇到的问题进行依次讲解。
1. 什么是 Watermark?
Watermark定义:当前系统认为的事件时间所在的真实时间。
2. 如何产生 Watermark?
Watermark产生:一般是从数据的事件时间来产生,产生策略可以灵活多样,最常见的包括使用当前事件时间的时间减去一个固定的delay,来表示可以可以容忍多长时间的乱序。
CREATE TABLE Orders(
user BIGINT,
product STRING,
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL 'S' SECOND
)WITH(...);//SQL
WatermarkStrategy
.<Tuple2<Long,String>>forBoundedOutOfOrdreness(Duration,ofSeconds(20))
.withTimestampAssigner((event,timestamp) -> event.f0);
3. 如何传递 Watermark?
Watermark传递:这个类似于上节课中介绍的Checkpoint的制作过程,传递就类似于Checkpoint的barrier,上下游task之间有数据传输关系的,上游就会将watermark传递给下游;下游收到多个上游传递过来的watermark后,默认会取其中最小值来作为自身的watermark,同时它也会将自己watermark传递给它的下游。经过整个传递过程,最终系统中每一个计算单元就都会实时的知道自身当前的watermark是多少。
4. 典型问题一
怎么观察一个任务中的watermark是多少,是否是正常的
- 一般通过Flink Web UI上的信息来观察当前任务的watermark情况
- 这个问题是生产实践中最容易遇到的问题,大家在开发事件时间的窗口任务的时候,经常会忘记了设置watermark,或者数据太少,watermark没有及时的更新,导致窗口一直不能触发。
5. 典型问题二
Per-partition / Per-subtask 生成watermark的优缺点
- 在Flink里早期都是per-subtask的方式进行watermark的生成,这种方式比较简单。但是如果每个source task如果有消费多个partition的情况的话,那多个partition之间的数据可能会因为消费的速度不同而最终导致数据的乱序程度增加。
- 后期(上面图中)就逐步的变成了per-partition的方式来产生watermark,来避免上面的问题。
6. 典型问题三
如果有部分partition/subtask会断流,应该如何处理
- 数据断流是很常见的问题,有时候是业务数据本身就有这种特点,比如白天有数据,晚上没有数据。在这种情况下,watermark默认是不会更新的,因为它要取上游subtask发来的watermark中的最小值。此时我们可以用一种IDLE状态来标记这种subtask,被标记为这种状态的subtask,我们在计算watermark的时候,可以把它先排除在外。这样就可以保证有部分partition断流的时候,watermark仍然可以继续更新。
7. 典型问题四
算子对于时间晚于watermark的数据的处理
- 对于迟到数据,不同的算子对于这种情况的处理可以有不同的实现(主要是根据算子本身的语义来决定的)
- 比如window对于迟到的数据,默认就是丢弃;比如双流join,对于迟到数据,可以认为是无法与之前正常数据join上。
Window
1. Window---基本功能
-
Window 分类
-
典型的window:
- Tumble Window(滚动窗口)
- Sliding Window (滑动窗口)
- Session Window (会话窗口)
-
其他window:
- 全局Window
- Count Window
- 累计窗口
- ...
-
2.滚动窗口
- 这是最常见的窗口类型,就是根据数据的时间(可以是处理时间,也可以是事件时间)划分到它所属的窗口中
windowStart = timestamp - timestamp % windowSize,这条数据所属的window就是[windowStart, windowStart + windowSize)(每条数据只会属于一个窗口)
- 在我们使用window的过程中,最容易产生的一个疑问是,window的划分是subtask级别的,还是key级别的。这里大家要记住,Flink 中的窗口划分是key级别的。 比如下方的图中,有三个key,那每个key的窗口都是单独的。 (每个key单独划分) 所以整个图中,一种存在14个窗口。
- 窗口的触发,是时间大于等于window end的时候,触发对应的window的输出(计算有可能提前就增量计算好了),目前的实现是给每个window都注册一个timer,通过处理时间或者事件时间的timer来触发window的输出。 (Window结束的时候一次性触发)
3.滑动窗口
- 了解了上面的TUMBLE窗口的基本原理后,HOP窗口就容易理解了。上面的TUMBLE窗口是每条数据只会落在一个窗口中。在HOP窗口中,每条数据是可能会属于多个窗口的(具体属于多少,取决于窗口定义的大小和滑动),比如下图中假设滑动是1h的话,那窗口大小就是2h,这种情况每条数据会属于两个窗口。除了这一点之外,其它的基本跟HOP窗口是类似的,比如也是key级别划分窗口,也是靠timer进行窗口触发输出。
- 每个key单独划分
- 每条数据可能属于多个窗口
- Window结束时间到达的时候一次性触发
4.会话窗口
- 会话窗口跟上面两种窗口区别比较大,上面两个窗口的划分,都是根据当前数据的时间就可以直接确定它所属的窗口。会话窗口的话,是一个动态merge的过程。一般会设置一个会话的最大的gap,比如10 min。
- 那某个key下面来第一条数据的时候,它的window就是 [
event_time, event_time + gap),当这个key后面来了另一条数据的时候,它会立即产生一个窗口,如果这个窗口跟之前的窗口有overlap的话,则会将两个窗口进行一个merge,变成一个更大的窗口,此时需要将之前定义的timer取消,再注册一个新的timer。
- 所以会话窗口要求所有的聚合函数都必须有实现merge。
- 每个key单独划分
- 每条数据会单独划分为一个窗口,如果Window之间有交集,则会对窗口进行merge
- Window结束时间到达的时候一次性触发