在Flink中支持三种不同的时间概念:处理时间(processing time)、事件时间(event time)、摄取时间(Ingestion time)。
而水印是衡量event time 进展的一种机制,它通常基于事件时间数据属性产生,水印作为数据流的一部分流动并带有时间戳t,代表该时间点以前的数据都已经到达。
watermark主要是用于处理乱序事件。而正确的处理乱序事件,通常用watermark机制结合window来实现。
流处理从事件产生,到流经source,再到operator,中间是有一个过程和时间的。虽然大部分情况下,流到operator的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络、背压等原因,导致乱序的产生(out-of-order或者说late element)。
但是对于late element,我们又不能无限期的等下去,必须要有个机制来保证一个特定的时间后,必须触发window去进行计算了。这个特别的机制,就是watermark。
为了处理事件时间,Flink需要知道事件的时间戳,这意味着流中的每个元素都需要分配其事件时间戳。这通常通过从元素中的某个字段访问/提取时间戳来完成。
时间戳分配与生成水印密切相关,水印告诉系统事件时间的进展。
有两种方法可以分配时间戳并生成水印:
- 直接在数据流源中
- 通过时间戳分配器/水印生成器:在Flink中,时间戳分配器还定义要发出的水印
流源可以直接为它们生成的元素分配时间戳,它们也可以发出水印。完成此操作后,不需要时间戳分配器。请注意,如果使用时间戳分配器,则将覆盖源提供的任何时间戳和水印。
要直接为源中的元素分配时间戳,源必须使用该collectWithTimestamp(...)
方法SourceContext
。要生成水印,源必须调用该emitWatermark(Watermark)
函数。
时间戳分配器获取流并生成带有带时间戳元素和水印的新流。如果原始流已经有时间戳和/或水印,则时间戳分配器会覆盖它们。
使用周期性水印
AssignerWithPeriodicWatermarks
分配时间戳并定期生成水印(可能取决于流元素,或纯粹基于处理时间)。
生成水印的间隔(每n毫秒)通过定义 ExecutionConfig.setAutoWatermarkInterval(...),
getCurrentWatermark()
每次调用分配器的方法,如果返回的水印非空并且大于先前的水印,则将发出新的水印。
带有标识的水印
要在某个事件表明可能生成新水印时生成水印,请使用 AssignerWithPunctuatedWatermarks
。对于此类,Flink将首先调用该extractTimestamp(...)
方法为元素分配时间戳,然后立即调用该checkAndGetNextWatermark(...)
元素上的 方法。
该checkAndGetNextWatermark(...)
方法传递方法中分配的时间戳extractTimestamp(...)
,并可以决定是否要生成水印。每当该checkAndGetNextWatermark(...)
方法返回非空水印,并且该水印大于最新的先前水印时,将发出该新水印。
Flink提供抽象,允许程序员分配他们自己的时间戳并发出他们自己的水印。更具体地说,可以通过实现其中一个AssignerWithPeriodicWatermarks
和AssignerWithPunctuatedWatermarks
接口来实现,具体取决于用例。简而言之,第一个将定期发出水印,而第二个基于传入记录的某些属性,例如,只要在流中遇到特殊元素。