Flink的事件时间处理
我们之前在流处理的概念中介绍过时间语义及其中的处理时间和事件时间
Flink对于处理时间和事件时间都支持,而且还提供操作简单,直观易用的原语,还支持一些表达能力很强的API,允许使用者使用自定义算子来实现更为高级的事件时间处理能力.
在事件时间使用模式下,Flink应用处理的所有记录都需要包含时间戳,时间戳将记录和特定时间点进行关联.只需要保证流记录中的时间戳会随着数据流得前进大致递增即可.毕竟事件时间往往存在着一定时间的乱序.
当Flink选择事件时间模式的时候,会根据记录的时间戳来触发时间相关算子的计算,比如时间窗口算子会根据记录关联的时间戳来分配到窗口中,内部采用了8字节的Long值来对时间戳进行编码,以元数据的方式来附加到记录上,内置算子将其转换为Unix时间戳.
Flink对于事件时间的使用,还需要使用水位线机制,利用水位线来推断一个任务的事件时间,比如基于时间窗口的任务会在事件时间超过窗口结束边界时进行最终的窗口计算并发出结果.
Flink中,水位线利用特殊记录实现了记录水位线,就像是带有额外时间戳的常规记录一样在数据流中流动.
水位线具有两个基本属性
需要单调递增, 且需要和记录的时间戳存在联系
在具有上面两个属性之后,当一个窗口收到了结束当前窗口的时间戳时候,会触发最终的计算
当收到一个违背水位线属性, 间戳小于或等于前一个水位线的记录,这种记录称为迟到记录.
那么水位线是一个特殊的记录,我们之前说过,而当任务接收到一个水位线时会执行以下操作:
1.更新内部事件时钟
2.任务的时间服务找到所有触发时间小于更新后事件时间的计时器,对于每个到期的计时器,都会调用回调函数
3.任务将更新后的事件发出
在Flink中,更为具体的架构为,一个任务在可能同时接收多个输入分区的记录和水位线,也可能将其发给多个输出分区.那么在任务内部,一个任务就会给每个输入分区维护一个水位线,每当有这个分区的新事件到来的时候,都会尝试更新对应分区的水位线,然后将事件时间的时钟调整为所有分区水位线中最小的值.如果水位线发生了变动,就会触发所有的计时器,然后将水位线发往所有的输出分区,实现一个水位线的广播.
那么一个分区可能会有多个输入流,对于这种存在并发的问题,Flink做的不好,其只是利用所有分区中最小值来计算事件时间时钟,并没有考虑是否来自不同的输入流.
而且,由于水位线的前进依赖于所有分区的水位线,那么一旦有一个分区的水位线没有前进,就会因此阻塞.而且多个输入流并存的情况下,往往快的输入流会等待慢的输入流.
而数据的时间来源我们之前一直没讲,其需要显式的分配时间戳,可以通过三种方式完成工作.
1. 在数据源中分配,利用SourceFunction在读入数据源的时候指定时间戳,并将水位线作为特殊记录在任何时间点发出.
2. DataStream API 提供了一个名为AssignerWithPeriodicWatermarks的用户自定义函数,其可以提取时间戳并周期性地获取当前水位线的查询请求
3. 定点分配器,提供的AssignerWithPunctuatedWatermarks自定义函数,根据特殊输入记录生成水位线的情况.
首选第一种,但是无论那种,都需要尽可能的靠近数据源算子.