Flink 1.8 Streaming Event Time 事件时间

 

事件时间/处理时间/进入时间(Event Time / Processing Time / Ingestion Time)

Flink在流处理程序中支持不同的时间概念。
  • 处理时间(processing time):处理时间是指执行相应操作的机器的系统时间。
当流处理程序基于处理时间运行时,所有基于时间的操作(如时间窗口)将使用运行相应运算符的机器的系统时钟。 每小时处理时间窗口将包括在系统时钟指示整个小时之间到达特定运算符的所有记录。 例如,如果应用程序在上午9:15开始运行,则第一个每小时处理时间窗口将包括在上午9:15到10:00之间处理的事件,下一个窗口将包括在上午10:00到11:00之间处理的事件,以此类推。 
 
处理时间是最简单的时间概念,不需要流和机器之间的协调。 它提供最佳性能和最低延迟。 但是,在分布式和异步环境中,处理时间不提供确定性,因为它容易受到记录到达系统的速度(例如从消息队列),记录在系统内的运算符之间流动的速度的影响,以及停电(计划或其他)。
  • 事件时间(event time):事件时间是每个事件在其生产设备上发生的时间。此时间通常在进入Flink之前嵌入记录中,并且可以从每个记录中提取该事件时间戳。 在事件时间,时间的进展取决于数据,而不是任何时钟。 事件时间程序必须指定如何生成事件时间水印,这是表示事件时间进度的机制。 该水印机制在下面的后面部分中描述。
在一个完美的世界中,事件时间处理将产生完全一致和确定的结果,无论事件何时到达或其它们的顺序。 但是,除非事件已知按顺序到达(按时间戳),否则事件时间处理会在等待无序事件时产生一些延迟。 由于只能等待一段有限的时间,因此限制了确定性事件时间应用程序的运行方式。
 
假设所有数据都已到达,事件时间操作将按预期运行,即使在处理无序或延迟事件或重新处理历史数据时也会产生正确且一致的结果。 例如,每小时事件时间窗口将包含带有落入该小时的事件时间戳的所有记录,无论它们到达的顺序如何,或者何时处理它们。 (有关更多信息,请参阅有关 迟到事件的部分。)
 
请注意,有时基于事件时间的程序处理实时数据时,它们将使用一些处理时间(processing time)操作,以保证它们及时进行。
  • 进入时间(Ingestion time): 进入时间是事件进入Flink的时间。 在源运算符处,每个记录将源的当前时间作为时间戳,并且基于时间的操作(如时间窗口)引用该时间戳。
进入时间在概念上位于事件时间和处理时间之间。与处理时间相比,它代价稍高,但可以提供更可预测的结果。 因为进入时间使用稳定的时间戳(在源处分配一次),所以对记录的不同窗口操作将引用相同的时间戳,而在处理时间中,每个窗口操作符可以将记录分配给不同的窗口(基于本地系统时钟和 任何传输延误)。
 
与事件时间相比,进入时间程序无法处理任何无序事件或延迟数据,但程序不必指定如何生成水印。
 
在内部,摄取时间与事件时间非常相似,但具有自动分配时间戳和自动生成水印功能。

设置时间特征(Setting a Time Characteristic)

Flink DataStream程序的第一部分通常设置基本时间特性。 该设置定义了数据流源的行为方式(例如,它们是否将分配时间戳),以及像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(...);
请注意,为了基于事件时间运行此示例,程序需要使用直接定义数据事件时间的源并自己输出水印,或者程序必须在源之后注入时间戳分配器和水印生成器。 这些函数描述了如何访问事件时间戳,以及事件流表现出的无序程度。
 
以下部分描述了时间戳和水印背后的一般机制。 有关如何在Flink DataStream API中使用时间戳分配和水印生成的指南,请参阅 Generating Timestamps / Watermarks
 

事件时间和水印(Event Time and Watermarks)

注意:Flink实现了数据流模型中的许多技术。 有关活动时间和水印的详细介绍,请查看以下文章。
 
支持事件时间的流处理器需要一种方法来衡量事件时间的进度。 例如,当事件时间超过一小时结束时,需要通知构建每小时窗口的窗口运算符,以便运算符可以关闭正在进行的窗口。
 
事件时间可以独立于处理时间(由时钟测量)进行。 例如,在一个程序中,运算符的当前事件时间可能略微落后于处理时间(考虑到接收事件的延迟),而两者都以相同的速度进行。 另一方面,通过快速转发已经在Kafka主题(或其它消息队列)中缓冲的一些历史数据,另一个流程序只需几秒钟处理几周的事件时间。
 
Flink中用于衡量事件时间进度的机制是水印。 水印作为数据流的一部分流动并带有时间戳t。 Watermark(t)声明事件时间已到达该流中的时间t,这意味着不应该有来自流的具有时间戳t'<= t的元素(即,具有更早或等于水印的时间戳的事件)。
 
下图显示了带有(逻辑)时间戳的事件流,以及内联流动的水印。 在该示例中,事件按顺序(相对于它们的时间戳),意味着水印是流中的周期性标记。
水印对于无序流是至关重要的,如下所示,其中事件不按时间戳排序。 通常,水印是一种声明,通过流中的那一点,到达某个时间戳的所有事件都应该到达。 一旦水印到达运算符,运算符就可以将其内部事件时钟提前到水印的值。
请注意,事件时间由一个新生成的流元素(或多个元素)继承,这些元素来自生成它们的事件或触发创建这些元素的水印。
 

并行流中的水印(Watermarks in Parallel Streams)

在源函数处或之后生成水印。 源函数的每个并行子任务通常独立地生成其水印。 这些水印定义了该特定并行源的事件时间。
 
当水印流过流媒处理程序时,它们会在他们到达的运算符处提前事件时间。 每当运算符提前其事件时间时,它就为其后继运算符生成下游的新水印。
 
一些运算符消费多个输入流; 例如union,或者跟随keyBy(...)或partition(...)函数的运算符。 这样的运算符的当前事件时间是其输入流的事件时间的最小值。 由于其输入流更新其事件时间,运算符也是如此。
 
下图显示了流经并行流的事件和水印的示例,以及跟踪事件时间的运算符。
请注意,Kafka源支持每分区水印,您可以在此处详细了解。
 

晚到元素(Late Elements)

某些元素可能违反水印条件,这意味着即使在Watermark(t)发生之后,也会出现更多具有时间戳t'<= t的元素。 实际上,在许多现实世界设置中,某些元素可以被任意延迟,从而无法指定某个事件时间戳的所有元素将发生的时间。 此外,即使迟到可以被限制,通常也不希望延迟太多水印,因为它在事件时间窗口的计算中引起太大延迟。
 
出于这个原因,流程序可能明确地预料一些晚到元素。 晚到元素是在系统的事件时钟(水印产生)之后到达的元素,事件时钟已经超过了晚到元素的时间戳。 有关如何在事件时间窗口中使用延迟元素的更多信息,请参阅 Allowed Lateness
 

空闲源(Idling sources)

目前,对于纯事件时间水印生成器,如果没有要处理的元素,则水印不能产出。 这意味着在输入数据存在间隙的情况下,事件时间将不会进行,例如窗口操作符将不会被触发,因此现有窗口将不能产生任何输出数据。
 
为了避免这种情况,可以使用定期水印分配器,它们不仅基于元素时间戳进行分配。 示例解决方案可以是在观察不到新事件一段时间之后切换到使用当前处理时间作为时间基础的分配器。
 
可以使用SourceFunction.SourceContext #markAsTemporarilyIdle将源标记为空闲。 有关详细信息,请参阅此方法的Javadoc以及StreamStatus。
 

调试水印(Debugging Watermarks)

有关在运行时调试水印的信息,请参阅 Debugging Windows & Event Time
 

运算符如何处理水印(How operators are processing watermarks)

作为一般规则,运算符需要在向下游转发之前完全处理给定的水印。 例如,WindowOperator将首先评估应该触发哪些窗口,并且只有在产生由水印触发的所有输出之后,水印本身才会被发送到下游。 换句话说,由于出现水印而产生的所有元素将在水印之前发出。
 
同样的规则适用于TwoInputStreamOperator。 但是,在这种情况下,运算符的当前水印被定义为其两个输入的最小值。
 
此行为的详细信息由OneInputStreamOperator#processWatermark,TwoInputStreamOperator#processWatermark1和TwoInputStreamOperator#processWatermark2方法的实现定义。
 

猜你喜欢

转载自www.cnblogs.com/sxpujs/p/11372639.html