Flink教程(二)——事件时间

事件时间/处理时间/摄入时间

        Flink支持流程序中的不同的时间通知。

        处理时间:处理时间是指执行操作的机器系统时间。

        当流程序运行在处理时间上时,所有的和时间相关的操作(比如时间窗口)都会使用运行各个操作运算的机器系统时钟。举个例子,一个小时单位的处理时间窗口会包含到达指定操作的所有记录在系统时钟是整个小时数之间。

        处理时间时最简单的时间通知,它需要流和机器之间的协作。它提供了最好的算法表现和最低的延迟。然而在分布式和异步环境中处理时间不提供可检测机制,因为这样会影响记录到达系统的速度(比如从消息队列中过来的),还有在系统中的算子之间的记录流动速度。

        事件时间:事件时间是每个发生在生产装置中的独立事件的时间。这个时间是在它们进入Flink和从记录中抽取的事件时间戳之前就包含在记录中的。一个小时单位的事件时间窗口包含拿到落地到那个小时内的事件时间戳,不管记录何时到达,不管他们的顺序如何。

        尽管对于无序的事件,事件时间都会给出正确的结果,晚来的事件,或者葱备份或者持久化的日志中回放记录。在事件时间中,依赖数据的处理时间,不是在任何现实时钟时间。事件时间程序必须确定怎样产生事件时间水印。而这个就是事件时间的信号流程的机制。下面来描述这个机制:

        事件时间流程总是发生在固定的延迟内,因为它自带的等待迟到的事件和无序的事件一段时间的属性。因为这个,事件时间程序经常和处理时间算子合并在一起用。

        摄入时间:摄入时间是进入Flink的事件的时间。在源算子中每个记录都能将源的当前时间作为一个时间戳,依赖于时间的操作就是这个时间戳。

        摄入时间是位于事件时间和处理时间之间的概念。相比于处理时间,它的开销更小,但是给予了更多预测结果。因为摄入时间使用了平衡时间戳(在源就分配了),对于记录的不同的窗口操作都会产生相同的时间戳,然而在处理时间上,每个窗口操作都会将记录分配给不同的窗口(基于本地系统时钟和任意传输时延)。

        相比较于事件时间,摄入时间程序不同处理无序和迟到的事件,但是这个程序没必要定义怎样去生成水印。

        对于内部来说,摄入时间更像是事件时间,但是有自动的时间戳分配和自动的水印生成。

设置时间特征

       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(...);
val env = StreamExecutionEnvironment.getExecutionEnvironment

env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

// alternatively:
// env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime)
// env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

val stream: DataStream[MyEvent] = env.addSource(new FlinkKafkaConsumer09[MyEvent](topic, schema, props))

stream
    .keyBy( _.getUser )
    .timeWindow(Time.hours(1))
    .reduce( (a, b) => a.add(b) )
    .addSink(...)

      为了在事件时间上运行这个例子,这个程序需要使用为数据直接定义事件时间和水印的注入,或者程序必须在源之后注入一个时间戳发放者和水印生成器。这些函数描述了怎样进入事件时间戳,还有乱序的事件流是怎样体现的。

      下面的一段描述了在时间戳和水印背后的通用机制。作为指导怎样在Flink DataStream API中使用时间戳分发器和水印生成器。

事件时间和水印

      Flink从数据流模型中实现了许多技术。为了做一个关于事件时间和水印的很好的介绍,我们看下下面的文章:

Streaming 101 by Tyler Akidau

The Dataflow Model paper

      一个支持事件时间的流处理需要一种处理事件时间流程的措施。比如说,一个窗口操作间里了时间的窗口需要通过当事件时间在一个小时的末了通过的时候,这样这个操作就可以在流程中关闭窗口。

      事件时间可以独立于处理时间进行。举个例子,在一个程序中一个操作的当前事件时间紧紧落后于处理时间,这两个时间几乎是一样的速度。从另一个方面讲,另外的流程序会花费仅仅几秒的处理时间来处理持续数周的事件时间。通过已经存储在一个kafka(或者其他的消息队列中间件) topic中的缓冲区域快进一些历史数据。

      Flink处理事件时间的流程的机制就是水印。水印作为数据流的一部分,获取一个时间戳t。这个水印表明了事件时间在这个流中有一个到达时间t。表明在流中没有一个带时间戳的元素,他的时间戳t‘是小于t的。

      下面的图展示了带有逻辑时间戳的事件流,水印流转其中。在这个例子中事件是排好序的,表明水印仅仅是在流中周期性的标记。

      水印对于无序的流来说至关重要,就像下面的图中展示的一样,事件不是按照时间戳排好序的。一般一个水印是通过流中的点来表示的,所有的在一个指定时间戳的事件都会到达。当一个水印到达一个操作时,那个操作就可以将它的内部事件时间校准到和水印的值一样。

并行流中的水印

      水印一般都是直接位于源函数之后的。每个源函数的并行子任务都会产生独立的水印。这些韩素音定义了在特定并行源上的事件时间。

      由于水印是流转在流程序中的,他们先于到达操作的事件时间。当一个操作先于事件时间时,他产生一个用于他的替代操作子的新水印下游。

      一些算子来自多个输入流:一个联结,举个例子,作为keyBy()或者partition()函数的参数的算子的事件事件时这个输入流中最小的。输入流和操作一起更新他们的事件时间。

      下面的图展示了一个在并行流中的事件和水印的例子,很明显能看到跟随事件时间的算子。

猜你喜欢

转载自my.oschina.net/u/216330/blog/1584844