一、Flink时间语义
在
Flink
的流式处理中,会涉及到时间的不同概念,如下图所示:
Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据 中,每一条日志都会记录自己的生成时间,
Flink
通过时间戳分配器访问事
件时间戳。
Ingestion Time
:是数据进入
Flink
的时间。
Processing Time:是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是
Processing Time
。
对于业务来说,要统计
1min
内的故障日志个数,哪个时间是最有意义的?——
eventTime
,因为我们要根据日志的生成时间进行统计。
1.2 EventTime 的引入
在
Flink
的流式处理中,绝大部分的业务都会使用
eventTime
,一般只在
eventTime
无法使用时,才会被迫使用
ProcessingTime
或者
IngestionTime
。
如果要使用
EventTime
,那么需要引入
EventTime
的时间属性,引入方式如下所
示:
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 从调用时刻开始给 env 创建的每一个 stream 追加时间特征
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
问题:乱序事件问题
二、水位线Watermark
注:最小的做全局watermark;分区watermark用最大的递增,比当前分区小的不做更新
注意:watermark是固定周期性或者是阶段性生成的,(数据稠密用固定周期,数据稀疏用阶段性)
2.1基本概念
我们知道,流处理从事件产生,到流经
source
,再到
operator
,中间是有一个过
程和时间的,虽然大部分情况下,流到
operator
的数据都是按照事件产生的时间顺
序来的,但是也不排除由于网络、分布式等原因,导致乱序的产生,所谓乱序,就
是指
Flink
接收到的事件的先后顺序不是严格按照事件的
Event Time
顺序排列的。
那么此时出现一个问题,一旦出现乱序,如果只根据
eventTime
决定
window
的
运行,我们不能明确数据是否全部到位,但又不能无限期的等下去,此时必须要有
个机制来保证一个特定的时间后,必须触发
window
去进行计算了,这个特别的机
制,就是
Watermark
。
Watermark
是一种衡量
Event Time
进展的机制。
Watermark
是用于处理乱序事件的
,而正确的处理乱序事件,通常用
Watermark
机制结合
window
来实现。
数据流中的
Watermark
用于表示
timestamp
小于
Watermark
的数据,都已经
到达了,因此,
window
的执行也是由
Watermark
触发的。
Watermark
可以理解成一个延迟触发机制,我们可以设置
Watermark
的延时
时长
t
,每次系统会校验已经到达的数据中最大的
maxEventTime
,然后认定
eventTime
小于
maxEventTime - t
的所有数据都已经到达,如果有窗口的停止时间等于
maxEventTime – t
,那么这个窗口被触发执行。
当
Flink
接收到数据时,会按照一定的规则去生成
Watermark
,这条
Watermark
就等于当前所有到达数据中的
maxEventTime -
延迟时长,也就是说,
Watermark
是
由数据携带的,一旦数据携带的
Watermark
比当前未触发的窗口的停止时间要晚,
那么就会触发相应窗口的执行。由于
Watermark
是由数据携带的,因此,如果运行
过程中无法获取新的数据,那么没有被触发的窗口将永远都不被触发。
上图中,我们设置的允许最大延迟到达时间为
2s
,所以时间戳为
7s
的事件对应
的
Watermark
是
5s
,时间戳为
12s
的事件的
Watermark
是
10s
,如果我们的窗口
1
是
1s~5s
,窗口
2
是
6s~10s
,那么时间戳为
7s
的事件到达时的
Watermarker
恰好触
发窗口
1
,时间戳为
12s
的事件到达时的
Watermark
恰好触发窗口
2
。
Watermark
就是触发前一窗口的“关窗时间”,一旦触发关门那么以当前时刻
为准在窗口范围内的所有所有数据都会收入窗中。
只要没有达到水位那么不管现实中的时间推进了多久都不会触发关窗。
2.2 Watermark 的引入
watermark
的引入很简单,对于乱序数据,最常见的引用方式如下:
dataStream.assignTimestampsAndWatermarks( new
BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.milliseconds(1000)) {
override def extractTimestamp(element: SensorReading): Long = {
element.timestamp * 1000
}
} )
Event Time
的使用一定要
指定数据源中的时间戳
。否则程序无法知道事件的事
件时间是什么
(
数据源里的数据没有时间戳的话,就只能使用
Processing Time
了
)
。
我们看到上面的例子中创建了一个看起来有点复杂的类,这个类实现的其实就
是分配时间戳的接口。
Flink
暴露了
TimestampAssigner
接口供我们实现,使我们可
以自定义如何从事件数据中抽取时间戳。
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 从调用时刻开始给 env 创建的每一个 stream 追加时间特性
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
val readings: DataStream[SensorReading] = env
.addSource(new SensorSource) .assignTimestampsAndWatermarks(new MyAssigner())
MyAssigner 有两种类型
AssignerWithPeriodicWatermarks
AssignerWithPunctuatedWatermarks
以上两个接口都继承自
TimestampAssigner
。