时间和水印
时间是流应用程序的另一个重要组成部分。大多数事件流都具有固有的时间语义,因为每个事件都在特定的时间点生成。此外,许多常见的流计算都是基于时间的,比如窗口聚合、会话、模式检测和基于时间的连接。
Flink提供了一组丰富的与时间相关的特性:
- 事件时间模式:流应用程序(使用事件时间语义处理流)基于事件的时间戳计算结果。因此,事件时间处理允许精确和一致的结果。
- 处理时间模式:除了事件时间模式,Flink还支持处理时间语义,它执行由处理机器的挂钟时间触发的计算。处理时间模式可以适用于某些具有严格低延迟要求的应用程序,这些应用程序可以容忍近似结果。
- 水印支持:Flink在事件时间应用程序中使用水印来推断时间。水印是一种灵活的机制,用来平衡结果的延迟和完整性。
- 迟到数据处理:当以事件时间模式处理带有水印的流时,可能会在所有相关事件到达之前完成计算。这样的事件称为迟到事件。Flink提供了多个处理迟到事件的选项,比如通过侧输出重新路由它们,并更新以前完成的结果。
时间概念
Flink Streaming API借鉴了谷歌数据流模型,它的流API明确支持三个不同的时间概念:
- 事件时间:事件发生的时间,由产生(或存储)事件的设备记录。
- 接入时间:Flink在接入事件时记录的时间戳。
- 处理时间:管道中特定操作符处理事件的时间。
设置时间特性
时间特性定义了系统如何为依赖时间的顺序和依赖时间的操作(如时间窗口)确定时间。默认情况下,Flink DataStream程序将使用EventTime(事件时间)。如果要改用处理时间,那么需要在一开始就设置时间特性。
Scala代码:
// 获得流执行环境 val env = StreamExecutionEnvironment.getExecutionEnvironment // 设置流的时间特性(这里设置为采用处理时间) env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
Java代码:
// 获得流执行环境 final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 设置流的时间特性(这里设置为采用处理时间) env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
注:在Flink 1.12之前,Flink DataStream默认使用的是处理时间。从Flink 1.12开始,默认的流时间特性已被更改为EventTime,因此不再需要调用此方法来启用事件时间支持。
当然也可以选择设置其他类型时间特性。
Scala代码:
env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime) env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
Java代码:
env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
事件时间和水印
支持事件时间的流处理器需要一种方法来度量事件时间的进度。例如,针对事件时间对数据进行窗口或排序的操作符必须缓冲数据,直到它们能够确保已接收到某个时间间隔的所有时间戳为止。这是由所谓的“时间水印”来处理的。
在Flink中测量事件时间进展的机制是水印(watermarks),水印是一种特殊类型的事件,是告诉系统事件时间进度的一种方式。水印流是数据流的一部分,并带有时间戳t。水印(t)声明事件时间已经到达该流中的时间t,这意味着时间戳t ' <= t(即时间戳更早或等于水印的事件)的流中不应该有更多的元素。
时间t的水印标记了数据流中的一个位置,并断言此时的流在时间t之前已经完成。为了执行基于事件时间的事件处理,Flink需要知道与每个事件相关联的时间,它还需要包含水印的流。水印就是系统事件时间的时钟。水印触发基于事件时间的计时器的触发。
下图显示了带有(逻辑的)时间戳的事件流,以及内联流动的水印。在这个例子中,事件是按顺序排列的(相对于它们的时间戳),这意味着水印只是流中的周期标记。
对于无序流,水印是至关重要的,如下图所示,其中事件不是按照它们的时间戳排序的。
例如,当操作符接收到w(11)这条水印时,可以认为时间戳小于或等于11的数据已经到达,此时可以触发计算。同样,当接收到w(17)这条水印时,可以认为时间戳小于等于17的数据已经到达,此时可以触发计算。
可以看出,水印的时间戳是单调递增的,时间戳为t的水印意味着所有后续记录的时间戳将大于t。一般来说,水印是一种声明,在流中的那个点之前,在某个时间戳之前的所有事件都应该已经到达。当水印到达运算符(算子)时,运算符可以将其内部事件时间时钟推进到水印的值。