Apache Flink 是一个分布式流处理引擎,具有直观而富于表现力的API来实现有状态流处理应用程序。
几十年来,数据和数据处理在企业中无处不在。多年来,数据的收集和使用一直在增长,很多公司都设计和构建了管理这些数据的基础架构。
大多数企业实现的传统架构区分了两种类型的数据处理
公司在日常业务活动中使用各种应用程序,如企业资源规划(ERP)系统、客户关系管理(CRM)软件和基于网络的应用程序。这些系统通常设计有独立的数据处理层(应用程序本身)和数据存储层(事务数据库系统)。
当应用程序需要更新或扩展时,这种应用程序设计可能会导致问题。
克服应用程序之间紧耦合的最新方法是微服务设计。微服务被设计成小型、独立的应用程序。他们遵循UNIX哲学:做一件事并把它做好。更复杂的应用程序是通过将几个微服务相互连接而构建的,这些微服务只通过标准接口进行通信,如RESTful HTTP连接。因为微服务彼此严格分离,并且只通过定义明确的接口进行通信,所以每个微服务都可以用不同的技术堆栈来实现,包括编程语言、库和数据存储。微服务和所有必需的软件和服务通常被打包并部署在独立的容器中。
存储在公司各种交易数据库系统中的数据可以提供关于公司业务运营的有价值的见解。然而,事务性数据通常分布在几个不相连的数据库系统中,如果可以联合分析,事务性数据更有价值。
对于分析类查询,数据通常复制到数据仓库中,而不是直接在事务数据库上运行分析查询,数据仓库是用于分析查询工作的专用数据存储。为了填充数据仓库,需要将事务数据库系统管理的数据复制到数据仓库中。将数据复制到数据仓库的过程称为提取-转换-加载(extract–transform–load,ETL)。
ETL过程
ETL过程可能非常复杂,通常需要技术上复杂的解决方案来满足性能要求。ETL过程需要定期运行,以保持数据仓库中的数据同步。
一旦数据被导入数据仓库,就可以对其进行查询和分析。通常,数据仓库上查询分为两类
数据仓库以批处理方式执行这两种查询。
时至今日,Apache Hadoop生态组件已经成为许多企业的信息技术基础架构中不可或缺的一部分。
任何处理事件流的应用,如果要支持跨多条记录的转换操作,都需要是有状态的,具有存储和访问中间数据的能力。
Apache Flink将应用程序状态存储在本地内存或本地嵌入式数据库中并且会定期向远程持久化存储来同步。
三类常见的有状态的流处理应用:
Apache Flink是很棒的第三代分布式流处理器。它以高吞吐量和低延迟大规模提供精确的流处理。
以下特性让Flink脱颖而出:
可以以不同的方式利用数据流图中的并行性。
首先,可以对某个算子的输入数据进行分区,并在数据子集上并行执行相同操作的任务。这种类型的并行称为数据并行。数据并行非常有用,因为它允许将大量计算数据分布到多个不同的物理节点上并行执行。
其次,可以让不同算子的任务 并行执行相同或不同数据的计算。这种类型的并行称为任务并行。使用任务并行,可以更好地利用集群的计算资源。
数据交换策略定义了数据项如何被分配给物理Dataflow图中的不同任务。常见的数据交换策略:转发、广播、基于键值、随机
下面看看如何将Dataflow的概念运用到并行数据流处理中。我们先给出数据流的定义:数据流是一个长度可能无限长的事件序列
数据流的例子如下:监控器产生的监控数据、传感器产生的测量数据、信用卡交易数据、气象站观测数据、搜索引擎搜索记录等
对于批处理应用程序,我们通常关心作业的总执行时间,或者我们的处理引擎读取输入、执行计算和写回结果需要多长时间。由于流应用程序连续运行,并且输入可能是无限的,因此在流处理中没有总执行时间的概念。取而代之的是,流处理必须尽可能快地为传入数据提供结果(延迟),同时还要应对很高的事件输入速率(吞吐)。我们用延迟和吞吐来表示这两方面的性能需求。
延迟表示处理一个事件所需的时间。本质上,它是接收事件到在输出中看到事件处理效果的时间间隔。
吞吐量是对系统处理能力的一种度量——它的处理速率。也就是说,吞吐量告诉我们系统每单位时间可以处理多少个事件。
延迟和吞吐 不是独立的指标。
降低延迟可提高吞吐量。如果一个系统可以更快地执行操作,它可以在相同的时间内执行更多的操作。而一个很好的方式就是并行处理
流处理引擎通常提供一组内置操作来接收、转换和输出数据流。这些操作可以用来构成Dataflow图来代表流式应用的逻辑。在本节中,我们将介绍最常见的流式操作。
操作可以是无状态的,也可以是有状态的。
数据接入和数据输出操作允许流处理器与外部系统通信。
数据接入是从外部系统获取原始数据并将其转换为适合处理格式的操作。实现数据接入逻辑的算子称为数据源(source)。
数据输出是以适合外部系统使用的形式产生输出的操作。实现数据输出的算子称为数据汇(sink),
转换操作是单程操作(single-pass),每个事件都独立处理。操作一个接一个地处理事件,并对事件数据进行一些转换,产生一个新的输出流。一般来说,转换操作比较简单,不用维护内部状态
转换操作的算子可以接受多个输入并产生多个输出流。他们还可以通过将一个流分成多个流或将多个流合并成一个流来修改数据流图的结构。
滚动聚合是针对每个输入事件 不断更新结果的聚合操作,比如总和、最小值和最大值。聚合操作是有状态的,并将当前状态与传入事件相结合以生成新的聚合值。
从一个无界事件流中创建长度有限的事件集(称为桶),并让我们对这些桶执行计算。
滚动窗口将事件分配到长度固定的不重叠的桶中。当窗口边界通过时,所有事件都被发送到一个计算函数进行处理。
滑动窗口将事件分配到固定大小的允许互相重叠的桶中。因此,一个事件可能属于多个桶。我们通过指定桶的长度和滑动间隔来定义滑动窗口。下图窗口长度为4,滑动间隔为3。
根据会话间隔(session gap)对事件进行分组,会话间隔定义了认为会话已关闭的非活动时间。(也就是如果用户在很长的一段时间内没有与服务器通信就认为他的会话已经关闭了)
我的图图.JPG
窗口操作与流处理中的两个主要概念密切相关:时间语义和状态管理。
流式场景中时间语义和不同的时间概念。流处理引擎如何基于乱序事件产生精确结果,如何使用数据流进行历史事件处理并实现“时间旅行”。
经典例子:爱丽丝在柏林地铁上玩手机游戏。游戏为1分钟内在线消除500个泡泡,这1分钟内通过网络不断向分析应用发送事件。爱丽丝玩了30秒后,地铁进入隧道手机断网了,爱丽丝继续玩,游戏产生的事件缓存在手机里。等地铁离开隧道,爱丽丝重新上线后,将缓存的事件发送给应用。请问上述的“1分钟”的含义是什么?需要包含爱丽丝离线的时间吗?
在线游戏这个场景展示了算子语义应该依赖事件实际发生时间 而非 应用收到事件的时间。
这个例子反映了流式应用可以使用两个不同概念的时间:处理时间(processing time)和事件时间(event time)。
处理时间是机器上本地时钟的时间。处理时间窗口包括在一段时间内碰巧到达窗口的所有事件,由机器的本地时钟测量。
事件时间是流中的事件实际发生的时间。事件时间通过附加到流事件的时间戳来判断。
即使事件有延迟,事件时间窗口也能准确地把事件分配到正确的窗口中,从而反映事情发生的真实情况。
无论数据流的处理速度有多快,事件到达算子的顺序是怎样的,事件时间窗口的计算将产生相同的结果。
通过依赖事件时间,即使是在无序数据的情况下,我们也可以保证结果的正确性。此外,当与可重放的流结合时,时间戳的确定性使你能够回到过去。也就是说,你可以重放一个流并分析历史数据,就像事件是实时发生的一样。
到目前为止,在我们关于事件时间窗口的讨论中,我们忽略了一个非常重要的方面:我们如何决定事件时间窗口的触发时机(什么时候停止收集并开始计算)?也就是说,我们要等多久才能确定我们已经收到了某个时间点之前发生的所有事件?考虑到分布式系统的不可预测性和由外部带来的各种延迟,这些问题没有绝对正确的答案。
水位线(watermark)是一种全局进度度量,它是一个时间点。它表明我们确信这个时间点之前的事件全部到达了。本质上,水位线提供了一个逻辑时钟,通知系统当前的事件时间。当操作员收到时间为T的水位线时,可以假设不会再收到时间戳小于T的事件(如果收到了,直接丢弃,不进入窗口的处理函数)。水位线对于事件时间窗口和处理无序事件的算子都是必不可少的。
水位线提供了结果可信度和延迟之间trade-off。
流处理系统会提供某种机制来处理在水位线之后到达的事件。
既然事件时间解决了我们所有的问题,为什么我们还要去关心处理时间?
事实是,在某些情况下,处理时间确实很有用。
状态在数据处理中无处不在。任何复杂一点的计算都需要它。为了产生结果,函数在一段时间或多个事件上累积状态(例如,计算聚集或检测模式)。有状态算子使用传入事件和内部状态来计算它们的输出并更新状态。
有状态算子实现的挑战:
流式作业中的算子状态非常重要,应防止出现故障。如果状态在故障期间丢失,恢复后的结果将是不正确的。流处理引擎不仅需要保证在出现任务故障时可以正常运行,还需要保证结果和算子状态的正确性。
对于输入流中的每个事件,任务执行以下步骤:
在这些步骤中的任何一个都可能发生故障,系统必须清楚地定义其在每种故障场景中的如何处理。例如,一个定义完整的流式处理系统需要明确以下问题:如果任务在第一步失败,事件会丢失吗?如果在更新了内部状态后失败了,恢复后还会再更新吗?而在上面这些情况下,输出还是正确的吗?
"结果保障"指的是流处理引擎内部状态的一致性,关注的是故障恢复后应用代码能够看到的状态值,与保证输出的一致性不是一回事。一旦数据从数据汇中写出,除非目标系统支持事务,否则结果的正确性将难以保证。
当任务发生故障时,最简单的方法就是不做任何事情来恢复丢失的状态,也不重放丢失的事件。至多一次只保证每个事件至多处理一次。换句话说,系统可以简单地丢弃事件,不做任何事情来确保结果的正确性。这种类型的保障也被称为“无保障”,因为即使是系统丢弃所有事件也可以提供这种保证。
在大多数现实世界的应用程序中,人们期望事件不会丢失。这种类型的保证被称为至少一次,这意味着所有事件都将被处理,并且其中一些事件有可能被处理多次。如果应用程序的正确性仅取决于信息的完整性,重复处理可能是可以接受的。
为了确保至少一次这种结果保障,需要有一种方法来重放(replay)事件——要么从源(source),要么从某个缓冲区(buffer)。
精确一次是最严格的保证,也很难实现。它意味着不仅不会有事件丢失,而且每个事件只允许处理一次。从本质上来说,精确一次意味着我们的应用程序将提供完全正确的结果,就好像从未发生过失败一样。
精确一次是以至少一次为前提的,因此数据重放机制必不可少。
而且在故障恢复之后,处理引擎应该知道一个事件的更新是否已经反映在状态上。有两种实现方式:
事务性更新是实现这一结果的一种方式,但是它们会导致大量的性能开销。
相反,Flink使用轻量级检查点(快照)机制来实现一次结果保证
目前为止以上的保障类型都仅限于流处理引擎自身的应用状态。实际的流处理应用中,除了流处理引擎也至少还要有一个数据来源组件和一个数据终点组件。端到端保证指的是整个数据处理流水线上的结果正确性。流水线上的每个组件都提供自己的保证,完整管道的端到端保证将由所有组件中最弱的那个组件来决定。有时候弱的保障可能会表现出强的语义,比如,你使用至少一次来求最大值或者最小值(属于幂等操作),管道的其他组件都使用精确一次,那么这个管道也是端到端精确一次的。
从较高层次介绍flink的架构,描述了flink是如何解决2中的流处理的问题的。重点介绍了flink的分布式架构,在流处理应用中如何处理时间和状态,讨论容错机制。
Flink是一个用于状态化并行数据流处理的分布式系统。Flink设置由多个进程组成,这些进程通常分布在多台机器上运行。
分布式系统需要解决的常见挑战是:
Flink本身并没有实现所有这些功能。它只关注于其核心功能——分布式数据流处理,但是利用了很多现有的开源中间件和框架来实现其他非核心部分。
Flink的搭建由四个不同的组件组成,它们一起工作来执行流应用程序。这些组件是JobManager(应用管理)、ResourceManager(资源管理)、TaskManager(工作进程)和Dispatcher(与用户交互)。由于Flink是用Java和Scala实现的,所以所有组件都运行在Java虚拟机(jvm)上。
TaskManager是flink的工作进程(worker process)。
Flink应用程序可以以两种不同的模式来部署。
在这种模式下,Flink应用程序被打包到一个JAR文件中,并由客户端提交给一个正在运行的服务。该服务可以是Flink Dispatcher、Flink JobManager或YARN的ResourceManager。
在这种模式下,Flink应用程序被绑定在一个应用程序特定的容器镜像中,比如Docker镜像。
TaskManager可以同时执行多个任务。
这些任务可以
TaskManager提供固定数量的处理槽来控制它能够并发执行的任务的数量。!!!一个处理槽可以执行应用程序的某个算子的一个并行任务!!!。
将多个不同算子的任务分配到同一个插槽的优点是这些任务可以在同一个进程中高效地交换数据,而不需要访问网络。
每个TaskManager是一个JVM,而每个Slot是JVM中的一个线程。TaskManager在同一个JVM进程中以多线程方式执行它的任务。线程比单独的进程更轻量,通信成本更低,但不会严格地将任务彼此隔离。因此,一个行为不正常的任务可以杀死整个TaskManager进程和运行在它上面的所有任务。