事件时间 /处理时间/摄入时间
Flink支持流操作中不同的时间概念。
-
处理时间: 处理时间是指执行相应操作的机器的系统时间。
当流程序在处理时间上运行时,所有基于时间的操作(如时间窗口)将使用运行相应操作符的机器的系统时钟。例如,每小时的处理时间窗口,将会包含系统时钟表示的一个小时内到达指定操作符的所有数据。
处理时间是最简单的时间概念,不需要协调流和机器。它提供了最佳的性能和最低的延迟。然而,在分布式和异构环境中,处理时间并不能提供确定性,因为它容易受到记录到达系统(例如来自消息队列)的速度的影响,以及系统内部操作符之间的记录流动速度。
-
事件时间: 事件时间是每个事件在其生产设备上产生的时间。这个时间通常是在它进入Flink之前包含在记录中的,并且可以从记录中提取事件时间戳。每小时事件时间窗口将包含携带的事件时间戳落入该小时的所有记录,不论事件何时到达,以及它们到达的顺序。
事件时间即使在无序事件,延迟事件,或者从数据备份或者持久化日志中重放数据时,也会给出正确的结果。在事件时间内,时间的进展依赖于数据,而不是任何墙上的时钟。事件时间程序必须指定如何生成事件时间水印,这是在事件时间内表示进展的机制。该机制如下所述。
事件时间处理通常存在一定的延迟,因为它的特性是为延迟事件和无序事件等待一定时间。因此,事件时间程序通常与处理时间操作相结合。 -
摄入时间: 摄入时间是指时间进入Flink的时间。在源操作符中,每个记录获取源的当前时间作为时间戳,而基于时间的操作(如时间窗口)则引用该时间戳。
摄入时间是一个位于事件时间与处理时间之间的概念。与处理时间相比,它稍微贵些,但给出了更可预测的结果。因为摄入时间使用稳定的时间戳(在源中分配),不同的窗口操作将会引用相同的时间戳,在事件时间内,每个窗口操作符可能将记录分配给不同的窗口(基于本地系统时钟和任意传输延迟)。
与事件时间相比,摄入时间程序不能处理任何乱序或延迟数据,但是程序不必指定如何生成水印。
在内部,摄入时间被视为事件时间,但是有自动的时间戳分配和水印生成。
设置时间特征
Flink DataStream程序第一部分通常是设置基本的时间特征。该设置定义了数据流Source的行为(例如,它们是否会分配时间戳),以及窗口操作(如KeyedStream.timeWindow(Time.seconds(30))所使用的时间概念。
下面的Flink示例程序每小时的时间窗口中聚合事件。窗口的行为适应时间特征。
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
// alternatively:
// env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime);
// env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
DataStream<MyEvent> stream = env.addSource(new FlinkKafkaConsumer09<MyEvent>(topic, schema, props));
stream
.keyBy( (event) -> event.getUser() )
.timeWindow(Time.hours(1))
.reduce( (a, b) -> a.add(b) )
.addSink(...);
注意,为了使示例以事件时间运行,程序需要要么使用直接定义数据事件时间并自己发送水印的Source,要么程序必须在Source之后注入一个时间戳生成器与水印生成器。这些函数描述了如何访问事件时间戳和事件流展示的乱序的程度。
下面的部分描述了时间戳和水印背后的机制。Flink DataStream API中有关如何使用时间戳分配和水印生成的指南,请看Generating Timestamps / Watermarks。
事件时间和水印
注意:Flink实现了Dataflow模型中的许多技术。要对事件时间和水印有一个很好的介绍,请看下面的文章。
- Streaming 101 by Tyler Akidau
- The Dataflow Model paper
支持事件时间的流处理器需要一种方法来度量事件时间的进度。例如,对于创建小时窗口的窗口操作符,当事件时间已经超过一个小时后需要通知该窗口操作符,以便操作符可以关闭处理中的窗口。
事件时间可以独立于处理时间(由壁钟测量)。例如,在一个程序中,操作符的当前事件时间可能会略滞后于处理时间(接收事件延迟而导致),然而两者都以相同的速度进行。另一方面,另一个流程序通过快速转发处理一些已经在Kafka主题(或另一个消息队列)中缓冲数周的历史数据,可能只需要几秒钟的处理。
在Flink中测量事件时间进度的机制是水印。水印携带一个时间戳t,并作为数据流的一部分。一个水印(t)宣称该流的事件时间已达t,这意味着该流中不应该再出现有元素的时间戳t ' < = t(即事件的时间戳大于或等于水印)。
下图显示了带有(逻辑)时间戳的事件流,以及内部流动的水印。在这个例子中,事件是有序的(基于它们的时间戳),这意味着水印只是流中的周期性标记。
水印对无序流至关重要,如下图所示,这些事件不是由它们的时间戳排序的。一般来说,水印是一种声明,即到流的此处,所有截止到指定时间戳的事件已经到达。一旦水印到达操作符,操作符可以将它内部的事件时钟更新到水印处。
并行流水印
水印是在源函数处或之后生成的。源函数的每个并行子任务通常独立地生成它的水印。这些水印定义了该特定并行源的事件时间。
当水印通过流程序时,它们会更新所到之处的操作符的事件时间。无论操作符什么时候更新它的事件时间,它会为它的后续操作符生成一个新的水印。
一些操作符消费多个输入流。例如union操作符,或者keyBy(…) 或 partition(…)函数之后的操作符。这样的操作符的当前事件时间是它的输入流的事件时间的最小值。当输入流更新它们的事件时间,操作符也会更新。
下图显示了事件和水印流经并行流,和操作符跟踪事件时间的示例。
延迟元素
一些元素可能会违背水印条件,这意味着即使是Watermark(t)之后生成的事件,一些元素的时间戳t ' <= t的情况也会发生。事实上,在真实世界的情况下,一些元素可以被任意延迟,从而无法指定一个时间,所有该事件时间戳之前的元素都已经生成。更进一步,即使延迟是有界的,延迟太多的水印也通常是不需要的,因为它会导致对事件时间窗口的评估延迟太多。
基于这个原因,流程序可能会明确的期望一些延迟元素。延迟元素是在系统事件时钟(由水印发出信号)之后到达的元素,且该时钟已经超过了延迟元素的时间戳。更多关于在事件时间窗口中如何处理延迟元素的信息参见Allowed Lateness
水印调试
请参阅调试窗口和事件时间部分,以便在运行时调试水印。