几个时间衡量维度

当我们在用Flink里的一些关于时间的操作 ( 比如 timeWindow() ) 时,应该以什么时间为判断标准是一个值得思考的问题。为什么这么说呢?因为不同的业务场景下所需的时间可能不太一样:

  • 例子1 :影片上映时间和实际剧本的时间可能是错开的,比如火影忍者前面几百集都是现在发生的,后面几百集都是回忆,按照正确的时间线应该是后面几百集先发生,再发生前面几百集
  • 例子2:在玩一些有排名的单机游戏时,如果当前没有网络,可能会把数据暂存在客户端,等连上网了后再发送数据,咱们系统要处理的时间是以客户端的为准还是收到的为准?为了用户体验,肯定是以客户端的为准,那么就少不了使用事件时间
  • 例子3:某个商品近一段时间的点击次数。点击事件如果晚了几分钟到有影响吗?如果误差范围能接受,那么久可以考虑用处理时间,速度快。

Flink提供了三个维度的时间衡量标准:

  1. 处理时间 ( Processing Time )

当任务设定了 Processing Time ,那么执行一些依赖时间的操作,比如timeWindow()。当数据进入这个算子后,就会使用 执行**timeWindow()**算子的机器时间 作为这条数据的时间,后续操作都依赖于这个时间(比如这条数据应该落到哪个窗口里)

  1. 事件时间 ( Event Time )

用户给定一个方法提取数据中的一个字段作为 Event Time,后续的算子都以这个时间为准

  1. 数据进入Flink的时间 ( Ingestion Time )

当一条数据进入**Source**算子时,这条数据的时间戳进入**Source**算子的机器时间决定

Event Time的缺陷

正常的Processing TimeIngestion Time维度的数据,必然都是按照客观的时间顺序排列的,不会出现【“先进来的数据”时间戳】 大于 【“后进来的元素”时间戳】:
点击查看【processon】
对于ProcessingTimeIngestionTime来说,它们的时间都取决于 Flink 时间,Flink 时间都是线性递增的,所以这两个时间维度的数据,必然都是按照时间顺序一个个来处理的。
但是对于EventTime就不一样了,EventTime是由数据生产者打上的标签,一条数据由生产者到达Flink经过了层层网络,不同的网络链路延迟不一样,可能会发生 【后发出的数据】 比 【先发出的数据】 早到,也就是“非正常时序的数据流向”所示那样。
所以对于Event Time来说,数据到达Flink的顺序是不确定的。不确定有什么关系嘛?窗口算子会提前关闭窗口,导致本应该落入的数据不会落入。

弥补EventTime的缺陷

怎么解决EventTime维度下的数据顺序不确定性?完全一致性的顺序很难甚至说做不到,但是保持阶段性的顺序一致性还是有可能的。什么意思呢?就是我们要做到1、2、3、4、5、6…这样的绝对顺序是很难的,但是可以尽可能保证一个长度为N秒的块之间保持顺序,比如【1、3、2、5、4】一个块,【9、7、5、6、10】一个块,块和块之间保持顺序(后来的块都大于前面的块)。

甚至在延迟比较小的情况下,能达到每个元素的完全顺序一致性

Flink提出了一种方式,不直接使用数据里的 EventTime 作为时间戳,而是在这个 EventTime 上做一些处理。比如一个数据是 16:47:30 秒进来的,我并不直接使用这个时间,而是做处理:将这个时间推前N秒(或者其他的计算方式),产生一个新的时间戳——16:47:28,咱们就用这个处理后的时间戳作为该数据进来时的时间,这个新的时间戳就称为——水位线(WaterMark)。只要产生了这个水位线,就表明这个时间以前的数据都已经到达了。

咱们用官方的示例说明一下,对于顺序的流来说,WaterMark的产生如下所示:
Flink 事件时间(Event Time) 和 处理时间(Processing Time) - 图1
如果是非顺序的流来说:
Flink 事件时间(Event Time) 和 处理时间(Processing Time) - 图2
依赖于对元素的时间戳处理产生的新时间戳,比如event timestamp=12的元素进来,产生了WaterMark=11,标识event timestamp=11及之前的数据都已经进来了,可以放心接收后续event timestamp>11的数据。

何时生成WaterMark

WaterMark可以在任何Source Function之后,只要在一个算子后定义了需要处理了Event Time,那么这个算子执行后就会产生WaterMark

并发流下的WaterMark

上面讲了 【在一个算子后定义处理Event Time的方法】,那如果一个算子本身是并行的怎么办?它并发产生了多个WaterMark给后续的算子,后续的算子该如何接收?后续的算子会拿 这一批并发产生的WaterMark中最小的一个作为WaterMark

一些超出预期的数据该怎么办

实际生产过程中,避免不了网络波动,可能会有个别几条数据姗姗来迟,可能会是迟到个10来秒(不在WaterMark提前的时间范围内),如果我不想丢掉这些迟到的数据怎么办?——使用allowLateness(),该方法允许窗口延迟几秒关闭,这并不是说让Windowsize变大,而是让Window迟点关闭。假设现在窗口大小是5S,某窗口接收数据的范围为 21:24:25 ~ 21:24:30,该窗口的水位线一旦超过21:24:30本应该关闭,但是如果设置了 allowLateness(5),那么就允许该窗口可以在 21:24:35 以后关闭。