一些基本概念介绍:
Event Time | 事件时间是每个事件在其生产设备上发生的时间 |
Ingestion Time | 摄取时间是数据进入Flink的时间 |
Processing Time | 处理时间是是指正在执行相应算子操作的机器的系统时间,默认的时间属性就是Processing Time |
[7]在Flink的流式处理中,绝大部分的业务都会使用EventTime,一般只在EventTime无法使用时,才会被迫使用ProcessingTime或者IngestionTime。默认情况下,Flink框架中处理的时间语义为ProcessingTime,如果要使用EventTime,那么需要引入EventTime的时间属性,引入方式如下所示:
import org.apache.flink.streaming.api.TimeCharacteristic
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
上面的这个只是为了表示当前的环境中有EventTIme属性,讲大白话就是输入的数据源中会有时间戳,不用想太多。
--------------------------------------------和时间相关的一些概念--------------------------------------------------------------------
假设nc -lk传入数据,intelliji处理数据,那么其实流处理的大概有这么几个时间相关的数据。
①终端输入数据的时间
②该数据中包含的时间
③intellij处理数据的时间
④window中的窗口宽度,slidingWindow还包括滑动时间宽度
⑤水位线时间, 这个是从上一条数据中得到的时间戳,另外还有水位线延迟时间(用来对付迟到的数据)
首先,①≠②,整个实验不关心①的时间,所以①我们不理会。
③我们一般也不关心(注意指的是一般情况下不关心,因为还是以数据源头中的时间戳中的Event Time为主)
④和⑤是我们关心范畴
所以主要是②④⑤
---------------------------------------------------------WaterMark和各种参数之间的关系--------------------------------------------------------------
什么是WaterMark呢,其实翻译过来就是水位线。
数据流好比水流(数据)从瀑布顶端倾泻而下,
而瀑布下方的水位线(数据生成的时间,Event Time)随着瀑布的流下而不断上升。
------------------------------------------------------------------------------------------------------------------------------------------------
Watermarks是基于已经收集的消息来估算是否还有消息未到达,本质上是一个时间戳。时间戳反映的是事件发生的时间,而不是事件处理的时间
所谓的事件(Event),讲人话其实就是一条数据。
1.设置StreamTime Characteristic为Event Time,即设置流式时间窗口
2.创建的DataStreamSource调用assignTimestampsAndWatermarks中设置WaterMarks种类(二选一):
①AssignerWithPeriodicWatermarks(周期性(一定时间间隔或者达到一定的记录条数)生成水印)
②AssignerWithPunctuatedWatermarks(数据流中每一个递增的 EventTime
都会产生一个 Watermark
)
数据示例:
实验类型(代码) | nc -lk 3456输入 | 实验结果 | 实验解析 |
tests | 测试::7> (tests,3) | 这个实验必须输入三个相同的字符串后,程序才会有反应 | |
later,6565 | windows:>>>:7> window:[1599362540000-1599362545000]:{ (later,6565,1) } | window后面的是实际处理时间。 later后面手动输入的时间戳被忽视 | |
later,32234 | windows:>>>:7> window:[1599364140000-1599364145000]:{ (later,32234,1) } windows:>>>:7> window:[1599364143000-1599364148000]:{ (later,32234,1) } | 一条数据出现在两个窗口中 | |
test,12312 test,235233 | windows:>>>:5> window:[1599366609562-1599366622057]:{ (test,12312,1),(test,235233,1) } | processtime间隔时间超过10s,就会输出上一个窗口(含10s所有输入内容) |
这里的例如1599366609562单位是ms
我们根据业务的需求还判断使用哪个时间类型,一般来说使用Event Time更多,比如:在统计最近5分钟的订单总金额时,我们需要的是真实的订单时间,而不是进入flink的时间或者是处理时间。
另外,生产环境中大多数情况下数据源头都会带有时间戳,时间戳经过flink处理提取得到Event Time。
上述三种窗口的事件事件写法与处理时间写法的对照(不包含窗口偏移设置)
| 事件时间(Event Time) | 处理时间 |
TumblingWindow | .window(TumblingEventTimeWindows.of(Time.seconds(5))) | .window(TumblingProcessingTimeWindows.of(Time.seconds(5))) |
SlidingWindow | .window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5))) | .window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5))) |
SessionWindow | .window(EventTimeSessionWindows.withGap(Time.minutes(10))) | .window(ProcessingTimeSessionWindows.withGap(Time.minutes(10))) |
上述三种窗口的事件事件写法与处理时间写法的对照(包含窗口偏移设置)
| 事件时间(Event Time) | 处理时间 |
TumblingWindow | .window(TumblingEventTimeWindows.of(Time.seconds(5),Time.seconds(3))) | .window(TumblingProcessingTimeWindows.of(Time.seconds(5),Time.seconds(3))) |
SlidingWindow | .window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5),Time.seconds(3))) | .window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5),Time.seconds(3))) |
SessionWindow | .window(EventTimeSessionWindows.withGap(Time.minutes(10),Time.seconds(3))) | .window(ProcessingTimeSessionWindows.withGap(Time.minutes(10),Time.seconds(3))) |
系统时间可以理解为是intellij处理数据的时间。
而EventTimeWindows那就是让窗口以事件中的时间戳(Event TIme)作为时间标尺了。
-----------------------------------------------下面是两个看似矛盾的说法--------------------------------------------------------------------------
[1]WaterMark为上一条数据的Event Time,并非当前的WaterMark
[2]WaterMark为当前数据的Event Time(根据实验输出来看)
为什么会有这么两种截然不同的说法呢?
这是因为:
[1]的WaterMark是在getCurrentWatermark()函数中输出的。
[2]的WaterMark是在extractTimestamp()函数中输出的。
所以其实不矛盾
-----------------------------------------------------对于包含WaterMark的程序的触发条件--------------------------------------
①并行度大于1的时候,需要各个节点都满足②③。
在多并行度的情况下,Watermark 会有一个对齐机制,这个对齐机制会取所有 Channel中最小的 Watermark。
②window条件要满足(例如每5条数据触发)、
③算子和定时器相关,需要waterMark高于定时器设置的时间。例如ctx.timerService().registerEventTimeTimer(value.getBizTime());
---------------------------------------------------------------------------------------------------------------------------------------
接收到水位线以前的的消息是不可避免的,这就是所谓的迟到事件。实际上迟到事件是乱序事件的特例,和一般乱序事件不同的是它们的乱序程度超出了水位线的预计,导致窗口在它们到达之前已经关闭。[8]
迟到事件出现时窗口已经关闭并产出了计算结果,因此处理的方法有3种:[8]
- 重新激活已经关闭的窗口并重新计算以修正结果(Side Output)。
- 将迟到事件收集起来另外处理(
Allowed Lateness
)。 - 将迟到事件视为错误消息并丢弃。
Flink 默认的处理方式是第3种直接丢弃,其他两种方式分别使用Side Output
和Allowed Lateness
。[8]
接收到水位线以前的的消息是不可避免的,这就是所谓的迟到事件。实际上迟到事件是乱序事件的特例,和一般乱序事件不同的是它们的乱序程度超出了水位线的预计,导致窗口在它们到达之前已经关闭。[8]
上面都是一些概念,实验方面的记录在[9]