如果要使用事件时间,还需要提供时间戳提取器和水印 Flink 将用于跟踪事件时间进度的生成器。这将在 下面关于使用水印的部分,但首先我们应该解释什么是水印。
让我们通过一个简单的例子来说明为什么需要水印,以及它们是如何工作的。
在此示例中,您有一个带有时间戳的事件流,这些事件的到达有些无序,如下所示 下面。显示的数字是指示这些事件实际发生时间的时间戳。第一个 到达的事件发生在时间 4,紧随其后的是较早发生的事件,即时间 2, 依此类推:
···23 19 22 24 21 14 17 13 12 15 9 11 7 2 4 →
现在假设您正在尝试创建一个流排序器。 这是一个应用程序,用于在流到达时处理流中的每个事件,并发出包含相同事件的新流,但按其时间戳排序。
一些观察:
(1) 您的流排序器看到的第一个元素是 4,但您不能立即将其释放为 排序流的第一个元素。它可能已无序到达,并且较早的事件可能 还到了。事实上,你对这个流的未来有一些神一样的知识,并且 您可以看到,您的流排序器应该至少等到 2 到达,然后再生成任何 结果。
一些缓冲和一些延迟是必要的。
(2)如果你做错了,你最终可能会永远等待。首先,分拣机从时间上看到了一个事件 4,然后是时间 2 的事件。时间戳小于 2 的事件会到达吗?或。 也许不是。你可以永远等待,永远看不到 1。
最终,你必须勇敢地发出 2 作为排序流的开始。
(3) 然后,您需要的是某种策略,该策略定义何时,对于任何给定的时间戳事件,何时 不要再等待早期事件的到来了。
这正是水印的作用——它们定义了何时停止等待早期事件。
Flink 中的事件时间处理依赖于插入特殊时间戳的水印生成器 元素添加到流中,称为水印。时间 t 的水印是断言 流现在(可能)在时间 t 中完成。
这个流排序器什么时候应该停止等待,并推出 2 来启动排序流?当 水印到达时的时间戳为 2 或更大。
(4) 您可以想象不同的策略来决定如何生成水印。
每个事件在一段时间后到达,这些延迟各不相同,因此某些事件的延迟超过 别人。一种简单的方法是假设这些延迟受某个最大延迟的限制。Flink 将此策略称为有界无序水印。不难想象更多 复杂的水印方法,但对于大多数应用程序来说,固定延迟已经足够好用了。
考虑水印的另一种方式是它们为您提供了流媒体的开发者 应用程序,控制延迟和完整性之间的权衡。与批处理不同, 在产生任何输入之前,人们能够完全了解输入 结果,通过流式处理,您最终必须停止等待看到更多输入,并产生一些输入 某种结果。
您可以积极地配置水印,并具有较短的有限延迟,从而 冒着在对输入的不完全了解的情况下产生结果的风险——即可能 错误的结果,产生得很快。或者,您可以等待更长的时间,并生成利用 对输入流有更全面的了解。
还可以实施混合解决方案,快速产生初步结果,然后 在处理其他(延迟)数据时提供这些结果的更新。这是一个很好的方法 一些应用程序。
迟到是相对于水印定义的。A 断言流已完成 直到时间t;此水印之后的任何事件,其时间戳为 ≤ T,则为延迟。Watermark(t)
为了执行基于事件时间的事件处理,Flink 需要知道与 每个事件,它还需要流包含水印。
动手练习中使用的出租车数据源会为您处理这些细节。但是在你的 自己的应用程序,您必须自己处理,这通常是通过实现来完成的 一个类,用于从事件中提取时间戳,并按需生成水印。这 最简单的方法是使用:WatermarkStrategy
Flink 具有非常富有表现力的窗口语义。
在本节中,您将了解:
在进行流处理时,很自然地想要计算有界子集的聚合分析 的流,以回答以下问题:
使用 Flink 计算窗口分析依赖于两个主要的抽象:将事件分配给窗口的窗口分配器(根据需要创建新的窗口对象)和应用于分配给窗口的事件的窗口函数。
Flink 的窗口化 API 也有触发器的概念,它决定了何时调用窗口 函数和 Evictors,可以删除窗口中收集的元素。
这些窗口分配器可能用于什么以及如何指定它们的一些示例:
TumblingEventTimeWindows.of(Time.minutes(1))
SlidingEventTimeWindows.of(Time.minutes(1), Time.seconds(10))
EventTimeSessionWindows.withGap(Time.minutes(30))
可以使用 、 、 和 之一指定持续时间。Time.milliseconds(n)
Time.seconds(n)
Time.minutes(n)
Time.hours(n)
Time.days(n)
基于时间的窗口分配器(包括会话窗口)同时出现在事件时间和处理中 时间的味道。这两种类型的时间窗口之间存在重大权衡。跟 处理时间窗口 您必须接受以下限制:
但具有延迟较低的优点。
使用基于计数的窗口时,请记住,这些窗口在批处理之前不会触发 已完成。没有超时和处理部分窗口的选项,但您可以实现 该行为自己使用自定义触发器。
全局窗口分配器将每个事件(使用相同的键)分配给同一全局窗口。这是 仅当您要使用自定义触发器执行自己的自定义窗口时才有用。在许多情况下 如果这看起来很有用,您最好使用另一节中所述的 a。ProcessFunction
对于如何处理窗口的内容,您有三个基本选项:
ProcessWindowFunction
Iterable
ReduceFunction
AggregateFunction
ReduceFunction
AggregateFunction
ProcessWindowFunction
以下是方法 1 和 3 的示例。每个实现都从每个传感器找到峰值 在 1 分钟的事件时间窗口内,并生成包含 .(key, end-of-window-timestamp, max_value)
在此实现中需要注意的几点:
windowState
并且是可以存储每个键、每个窗口或全局的位置 该键的所有窗口的每个键信息。例如,如果要记录有关当前窗口的某些内容并在处理后续窗口时使用它,这可能很有用。globalState