[一] 故事背景
Flink自2011年5月发布初始版本,到2019年开始普遍被各大互联网公司使用。在2020年初发布1.10.0,作为 Flink 社区迄今为止规模最大的一次版本升级,Flink 在增强流式 SQL 处理能力的同时也具备了成熟的批处理能力。
[二] 原理描述
Flink核心是一个流式的数据流执行引擎,支持流处理和批处理。
1、统一的批处理与流处理系统
在大数据处理领域,批处理任务与流处理任务一般被认为是两种不同的任务,一个大数据项目一般会被设计为只能处理其中一种任务,例如Apache Storm、Apache Smaza只支持流处理任务,而Aapche MapReduce、Apache Tez、Apache Spark只支持批处理任务。Spark Streaming是Apache Spark之上支持流处理任务的子系统,看似一个特例,实则不然——Spark Streaming采用了一种micro-batch的架构,即把输入的数据流切分成细粒度的batch,并为每一个batch数据提交一个批处理的Spark任务,所以Spark Streaming本质上还是基于Spark批处理系统对流式数据进行处理。
Flink通过其灵活的执行引擎,Flink能够同时支持批处理任务与流处理任务。
在执行引擎这一层,流处理系统与批处理系统最大不同在于节点间的数据传输方式。对于一个流处理系统,其节点间数据传输的标准模型是:当一条数据被处理完成后,序列化到缓存中,然后立刻通过网络传输到下一个节点,由下一个节点继续处理。而对于一个批处理系统,其节点间数据传输的标准模型是:当一条数据被处理完成后,序列化到缓存中,并不会立刻通过网络传输到下一个节点,当缓存写满,就持久化到本地硬盘上,当所有数据都被处理完成后,才开始将处理后的数据通过网络传输到下一个节点。
Flink的执行引擎采用了一种十分灵活的方式,同时支持了这两种数据传输模型。Flink以固定的缓存块为单位进行网络数据传输,用户可以通过缓存块超时值指定缓存块的传输时机。如果缓存块的超时值为0,则Flink的数据传输方式类似上文所提到流处理系统的标准模型,此时系统可以获得最低的处理延迟。如果缓存块的超时值为无限大,则Flink的数据传输方式类似上文所提到批处理系统的标准模型,此时系统可以获得最高的吞吐量。同时缓存块的超时值也可以设置为0到无限大之间的任意值。缓存块的超时阈值越小,则Flink流处理执行引擎的数据处理延迟越低,但吞吐量也会降低,反之亦然。通过调整缓存块的超时阈值,用户可根据需求灵活地权衡系统延迟和吞吐量。
2、Flink流处理的容错机制
批处理系统比较容易实现容错机制,由于文件可以重复访问,当某个任务失败后,重启该任务即可。但是到了流处理系统,由于数据源是无限的数据流,从而导致一个流处理任务执行几个月的情况,将所有数据缓存或是持久化,留待以后重复访问基本上是不可行的。
Flink基于分布式快照与可部分重发的数据源实现了容错。用户可自定义对整个Job进行快照的时间间隔,当任务失败时,Flink会将整个Job恢复到最近一次快照,并从数据源重发快照之后的数据。Flink的分布式快照实现借鉴了Chandy和Lamport在1985年发表的一篇关于分布式快照的论文,其实现的主要思想如下:
按照用户自定义的分布式快照间隔时间,Flink会定时在所有数据源中插入一种特殊的快照标记消息,这些快照标记消息和其他消息一样在DAG中流动,但是不会被用户定义的业务逻辑所处理,每一个快照标记消息都将其所在的数据流分成两部分:本次快照数据和下次快照数据。
快照标记消息沿着DAG流经各个操作符,当操作符处理到快照标记消息时,会对自己的状态进行快照,并存储起来。操作符对自己的状态快照并存储可以是异步与增量的操作,并不需要阻塞消息的处理。
[三] 基本架构
当Flink集群启动后,首先会启动一个 JobManger 和一个或多个的 TaskManager。由 Client 提交任务给 JobManager,JobManager 再调度任务到各个 TaskManager 去执行,然后 TaskManager 将心跳和统计信息汇报给 JobManager。TaskManager 之间以流的形式进行数据的传输。上述三者均为独立的 JVM 进程。
● Flink Program加载用户提交的任务代码,解析并生成任务执行拓扑图。Client为提交 Job 的客户端,可以是运行在任何机器上(与 JobManager 环境连通即可)。提交 Job 后,Client 可以结束进程(Streaming的任务),也可以不结束并等待结果返回。
● JobManager基于任务执行拓扑图,生成相应的物理执行计划,将执行计划发送给TaskManager执行。除此之外,JobManager还负责协调checkpoint的生成,不断地从TaskManager收集Operator的状态,并周期性生成checkpoint。
● TaskManager在启动的时候就设置好了槽位数(Slot),每个 slot 能启动一个 Task,Task 为线程。从 JobManager 处接收需要部署的 Task,部署启动后,与自己的上游建立 Netty 连接,接收数据并处理。
每个TaskManger上运行一个jvm进程。每个TaskSlot运行一个线程,瓜分Task Manager的内存。
Flink Program、JobManager、TaskManager之间,使用Akka框架(Actor System)进行通信, 通过发送消息驱动任务的推进。
[三] 执行图
Flink中的执行图可以分成四层:StreamGraph -> JobGraph -> ExecutionGraph -> 物理执行图。
● StreamGraph:是根据用户通过 Stream API 编写的代码生成的最初的图。用来表示程序的拓扑结构。
● JobGraph:StreamGraph经过优化后生成了 JobGraph,提交给 JobManager 的数据结构。主要的优化为,将多个符合条件的节点 chain 在一起作为一个节点,这样可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗。
● ExecutionGraph:JobManager 根据 JobGraph 生成ExecutionGraph。ExecutionGraph是JobGraph的并行化版本,是调度层最核心的数据结构。
●物理执行图:JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构。
[四] API
Flink自底向上在不同的抽象级别提供了多种 API,Flink 根据抽象程度分层,提供了三种不同的 API。每一种 API 在简洁性和表达力上有着不同的侧重,并且针对不同的应用场景。
DataSet(同DataStream层 ) : 对静态数据(如文件、集合等)进行批处理操作,将静态数据抽象成分布式的数据集,用户可以通过各种算子对分布式数据集进行处理。
DataStream API:对数据流进行流处理操作,将流式的数据抽象成分布式的数据流,用户可以方便地对分布式数据流进行各种操作。
Flink已经拥有了强大的 DataStream/DataSetAPI,满足流计算和批计算中的各种场景需求,但是API 有一定难度并且需要语言基础,同时flink也想做到流任务与批任务的API统一,我们还需要提供一种关系型的 API 来实现 Flink API 层的流与批的统一,那么这就是 Flink 的 Table & SQL API。
Table API:对结构化数据进行查询操作,将结构化数据抽象成关系表,并通过类SQL的DSL对关系表进行各种查询操作。
SQL API:Flink SQL 是基于 Apache Calcite 的实现的,Calcite 实现了 SQL 标准解析。SQL 查询是一个完整的 sql 字符串来查询。
Table API和 SQL 查询可以很容易的混合使用,因为它们的返回结果都是 Table 对象。一个基于 Table API 的查询可以基于一个 SQL 查询的结果。同样地,一个 SQL 查询可以被定义一个 Table API 注册 TableEnvironment 作为 Table 的查询结果。