新一代流式计算引擎 Flink

Apache Flink 是为分布式、高性能、随时可用以及准确的流处理应用程序打造的开源流处理框架。”Flink不仅能提供同时支持高吞吐和exactly-once 语义的实时计算,还能提供批量数据处理.

批处理与流处理

Flink 是如何同时实现批处理与流处理的呢?答案是,Flink 将批处理(即处
理有限的静态数据)视作一种特殊的流处理。Flink 的核心计算构造是图1-4 中的Flink Runtime 执行引擎,它是一个分布式系统,能够接受数据流程序并在一台或多台机器上以容错方式执行。Flink Runtime 执行引擎可以作为YARN(Yet Another Resource Negotiator)的应用程序在集群上运行,也可以在Mesos 集群上运行,还可以在单机上运行(这对于调试Flink 应用程序来说非常有用)。

Flink 技术栈的核心组成部分。值得一提的是,Flink 分别提供了面向流处理的接口(DataStream API)和面向批处理的接口(DataSet API)。因此,Flink 既可以完成流处理,也可以完成批处理。Flink 支持的拓展库涉及机器学习(FlinkML)、复杂事件处理(CEP),以及图计算(Gelly),还有分别针对流处理和批处理的Table API.

消息传输层和流处理层

  • 消息传输层从各种数据源(生产者)采集连续事件产生的数据,并传输给订阅了这些数据的应用程序和服务(消费者)。
  • 处理层有3 个用途:①持续地将数据在应用程序和系统间移动;②聚合并处理事件;③在本地维持应用程序的状态。

Flink 项目的架构有两个主要组成部分:消息传输层和由Flink 提供的流处理层。消息传输层负责传输连续事件产生的消息,能够提供消息传输的系统包括Kafka 和MapR Streams。MapR Streams 是MapR 融合数据平台的一个主要组成部分,它兼容Kafka API.

Flink中的时间概念

在流处理中,主要有两个时间概念
• 事件时间,即事件实际发生的时间。更准确地说,每一个事件都有一个与它相关的时间戳,并且时间戳是数据记录的一部分(比如手机或者服务器的记录)。事件时间其实就是时间戳。
• 处理时间,即事件被处理的时间。处理时间其实就是处理事件的机器所测量的时间。

以《星球大战》系列电影为例。首先上映的3 部电影是该系列中的第4、5、6 部(这是事件时间),它们的上映年份分别是1977 年、1980 年和1983 年(这是处理时间)。之后按事件时间上映的第1、2、3、7 部,对应的处理时间分别是1999 年、2002 年、2005 年和2015 年。由此可见,事件流的顺序可能是乱的(尽管年份顺序一般不会乱)。

通常还有第3 个时间概念,即摄取时间,也叫作进入时间。它指的是事件进入流处理框架的时间。缺乏真实事件时间的数据会被流处理器附上时间戳,即流处理器第一次看到它的时间(这个操作由source 函数完成,它是程序的第一个处理节点)。

Flink中的窗口机制

窗口是一种机制,它用于将许多事件按照时间或者其他特征分组,从而将每一组作为整体进行分析(比如求和)。

时间窗口

是最简单和最有用的一种窗口。它支持滚动和滑动。举一个例子,假设要对传感器输出的数值求和。

一分钟滚动窗口收集最近一分钟的数值,并在一分钟结束时输出总和

一分钟滑动窗口计算最近一分钟的数值总和,但每半分钟滑动一次并输出结果

第一个滑动窗口对9、6、8 和4 求和,得到27。半分钟后,窗口滑动,然后对8、4、7 和3 求和,得到22,照此类推。

在Flink 中,一分钟滚动窗口的定义如下。

stream.timeWindow(Time.minutes(1))

每半分钟(即30 秒)滑动一次的一分钟滑动窗口如下所示

stream.timeWindow(Time.minutes(1), Time.seconds(30))4.4.2
计数窗口

Flink 支持的另一种常见窗口叫作计数窗口。采用计数窗口时,分组依据不再是时间戳,而是元素的数量。例如,图4-6 中的滑动窗口也可以解释为由4 个元素组成的计数窗口,并且每两个元素滑动一次。滚动和滑动的计数窗口分别定义如下。

stream.countWindow(4)
stream.countWindow(4, 2)

虽然计数窗口有用,但是其定义不如时间窗口严谨,因此要谨慎使用。时间不会停止,而且时间窗口总会“关闭”。但就计数窗口而言,假设其定义的元素数量为100,而某个key 对应的元素永远达不到100 个,那么窗口就永远不会关闭,被该窗口占用的内存也就浪费了。一种解决办法是用时间窗口来触发超时.

会话窗口

Flink 支持的另一种很有用的窗口是会话窗口。第3 章提到过这个概念。会话指的是活动阶段,其前后都是非活动阶段,例如用户与网站进行一系列交互(活动阶段)之后,关闭浏览器或者不再交互(非活动阶段)。会话需要有自己的处理机制,因为它们通常没有固定的持续时间(有些30 秒就结束了,有些则长达一小时),或者没有固定的交互次数(有些可能是3 次点
击后购买,另一些可能是40 次点击却没有购买)。

Flink中的水印机制

支持事件时间对于流处理架构而言至关重要,因为事件时间能保证结果正确,并使流处理架构拥有重新处理数据的能力。当计算基于事件时间时,如何判断所有事件是否都到达,以及何时计算和输出窗口的结果呢?换言之,如何追踪事件时间,并知晓输入数据已经流到某个事件时间了呢?为了追踪事件时间,需要依靠由数据驱动的时钟,而不是系统时钟。以图4-5 中的一分钟滚动窗口为例。假设第一个窗口从10:00:00 开始(即从10 时0 分0 秒开始),需要计算从10:00:00 到10:01:00 的数值总和。当时间就是记录的一部分时,我们怎么知道10:01:00 已到呢?换句话说,我们怎么知道盖有时间戳10:00:59 的元素还没到呢?

Flink 通过水印来推进事件时间。水印是嵌在流中的常规记录,计算程序通过水印获知某个时间点已到。对于上述一分钟滚动窗口,假设水印标记时间为10:01:00(或者其他时间,如10:03:43),那么收到水印的窗口就知道不会再有早于该时间的记录出现,因为所有时间戳小于或等于该时间的事件都已经到达。这时,窗口可以安全地计算并给出结果(总和)。水印使事件时间与处理时间完全无关。迟到的水印(“迟到”是从处理时间的角度而言)并不会影响结果的正确性,而只会影响收到结果的速度。

水印是如何生成的

在Flink 中,水印由应用程序开发人员生成,这通常需要对相应的领域有一定的了解。完美的水印永远不会错:时间戳小于水印标记时间的事件不会再出现。在特殊情况下(例如非乱序事件流),最近一次事件的时间戳就可能是完美的水印。启发式水印则相反,它只估计时间,因此有可能出错,即迟到的事件(其时间戳小于水印标记时间)晚于水印出现。针对启发式
水印,Flink 提供了处理迟到元素的机制。设定水印通常需要用到领域知识。举例来说,如果知道事件的迟到时间不会超过5 秒,就可以将水印标记时间设为收到的最大时间戳减去5 秒。另
一种做法是,采用一个Flink 作业监控事件流,学习事件的迟到规律,并以此构建水印生成模型。

如果水印迟到得太久,收到结果的速度可能就会很慢,解决办法是在水印到达之前输出近似结果(Flink 可以实现)。如果水印到达得太早,则可能收到错误结果,不过Flink 处理迟到数据的机制可以解决这个问题。上述问题看起来很复杂,但是恰恰符合现实世界的规律——大部分真实的事件流都是乱序的,并且通常无法了解它们的乱序程度(因为理论上不能预见未来)。水印是唯一让我们直面乱序事件流并保证正确性的机制;否则只能选择忽视事实,假装错误的结果是正确的。


  目录