Flink 中可以使用一套 API 完成对有界数据集以及无界数据的统一处理,而无界数据集的处理一般会伴随着对某些固定时间间隔的数据聚合处理。比如:每五分钟统计一次系统活跃用户、每十秒更新热搜榜单等等
这些需求在 Flink 中都由 Window 提供支持,Window 本质上就是借助状态后端缓存着一定时间段内的数据,然后在达到某些条件时触发对这些缓存数据的聚合计算,输出外部系统。
实际上,有的时候对于一些实时性要求不高的、下游系统无法负载实时输出的场景,也会通过窗口做一个聚合,然后再输出下游系统。
时间类型
Flink 是基于事件流的实时处理引擎,那么流入系统的每一件事件都应该有一个时间,Flink 提供以下四种时间类型来定义你的事件时间:
-
Event Time:这是我们最常用的时间类型,它表示事件真实发生时的时间(比如你点击一个按钮,就是点击的一瞬间的那个时间)
-
Storage Time:不常用,表示事件以消息的形式进入队列时的时间
-
Ingestion Time:不常用,表示事件进入 Flink Source 的时间
-
Processing Time:相对常用一些,表示事件实际进入到 window 算子被处理的时间
以上四种实际上用的最多的还是 EventTime,ProcessingTime 偶尔会用一用。
因为 EventTime 是描述事件真实发生的时间,我们知道事件发生是有顺序的,但经过网络传输后不一定能保证接收顺序。比如:你先买了 A 商品,再买了 B 商品,那么其实有很大可能 Flink 先收到 B 商品的购买事件,再收到 A 的。通过 EventTime 就可以保证即便 A 事件后到来我也知道它是先发生的。
而 ProcessingTime 描述的是事件被处理时的时间,准确来说并不是事件真实发生的时间,所以它往往在一些不关注事件到达顺序的情境中使用。
Watermark 水位线
Watermark 在很多系统中都有应用,可能各个系统的叫法不同,但这种思想还是比较常见的。比如:Kafka 中副本同步机制中的高水位、MySQL 事务隔离机制中可见事务的高低水位等等。
在 Flink 中 Watermark 描述的也是一种水位线的概念,他表示水位线之下的所有数据都已经被 Flink 接收并处理了。
窗口的触发一般就会基于 Watermark 来实现,水位线动态更新,当达到某某条件就触发哪些窗口的计算。
关于 Watermark 如何更新,Flink 是开放给你实现的,当然它也提供了一些默认实现。
Timestamp 的抽取
如果你指定 Flink 需要使用 EventTime,那么你就需要在 WatermarkStrategy 策略中通过 withTimestampAssigner 指定如何从你的事件中抽取出 Timestamp 作为 EventTime。比如:
Watermark 的生成
Watermark 的生成方式本质上是有两种:周期性生成和标记生成。
/**
* {@code WatermarkGenerator} 可以基于事件或者周期性的生成 watermark。
*
* <p><b>注意&