注:本文会持续补充。
背景介绍
Flink 的前世今生
Apache Flink 是德国柏林工业大学的几个博士生和研究生从学校开始做起来的项目,早期叫做 Stratosphere。刚开始,Flink 是做 Batch 计算的,但是在2014年,StratoSphere 里面的核心成员孵化出 Flink,同年将Flink 捐赠 Apache 基金会,并在后来成为 Apache 的顶级大数据项目,同时Flink计算的主流方向被定位为 Streaming,即用流式计算来做所有大数据的计算,这就是Flink技术诞生的背景。
Flink 是什么?一句话介绍就是:Flink 是一个低延迟、高吞吐、统一的大数据计算引擎。
2015 年,阿里巴巴开始尝试使用 Flink。但是阿里的业务体量非常庞大,挑战也很多。于是阿里巴巴实时计算团队决定在阿里内部建立一个 Flink 分支 Blink,并对 Flink 进行大量的修改和完善,让其适应阿里巴巴这种超大规模的业务场景。简单地说,Blink 就是阿里巴巴开发的基于开源 Flink 的阿里巴巴内部版本。
2019年 1月9日,阿里巴巴以 9000 万欧元的价格收购了 Apache Flink 的核心团队组建的创业公司 Data Artisans 。Data Artisans 之前专为企业提供高吞吐、低延迟的大规模数据解决方案,以便企业能够即时响应数据,从而更合理、更快速地做出业务决策。
2019 年 1 月底,阿里巴巴内部 Flink 版本 Blink 正式开源:https://github.com/apache/flink/tree/blink, 目前开源的方式是 分支回推。
我们把代码贡献出来,是为了让大家能先尝试一些他们感兴趣的功能。Blink 永远不会单独成为一个独立的开源项目来运作,他一定是 Flink 的一部分。开源后我们期望能找到办法以最快的方式将 Blink 合并到 Flink 中去。Blink 开源只有一个目的,就是希望 Flink 做得更好。Apache Flink 是一个社区项目,Blink 以什么样的形式进入 Flink 是最合适的,怎么贡献是社区最希望的方式,我们都要和社区一起讨论。
在过去的一段时间内,我们在 Flink 社区征求了广泛的意见,大家一致认为将本次开源的 Blink 代码作为 Flink 的一个分支直接推回到 Apache Flink 项目中是最合适的方式。
批流计算统一
在典型的大数据的业务场景下,数据业务最通用的做法是:选用批处理的技术处理全量数据,采用流式计算处理实时增量数据。在绝大多数的业务场景之下,用户的业务逻辑在批处理和流处理之中往往是相同的。但是,用户用于批处理和流处理的两套计算引擎是不同的。
批流统一是潮流,会让世界更美好。为什么?因为有了这么一套统一的大数据引擎技术,用户只需要根据自己的业务逻辑开发一套代码,在各种不同的场景下,不管是全量数据还是增量数据,亦或者实时处理,都可以都到全部支持。
Flink 是一个可以同时支持流数据和批数据的分布式处理引擎。对 Flink 而言,其所要处理的主要场景是流数据,批数据只是流数据的一个极限特例而已。目前发展的一个方向是:在编程模型上做进一步的统一,让批流处理无差别。
Apache Beam 介绍
既然提到统一的编程模型,就顺便介绍下 Google 的 Beam 吧。
Apache Beam 是 Google 开源(2016年2月)的一个统一编程框架,它本身不是一个流式处理平台,而是提供了统一的编程模型,帮助用户创建自己的数据处理流水线,实现「」可以在任意执行引擎之上运行批处理和流式处理任务。它包含:
- 一个统一的编程模型:涵盖批处理和流处理
- 一系列
SDK
(Beam SDK),支持 Java 和 Python - 一系列
Runner
,让其编程模型运行在不同底层处理引擎(Google Cloud Dataflow,Spark,Flink等)
Google的想法可能是这样的:后期大数据处理平台变得很多之后,各领域需要使用不同的平台处理不一样的业务,这样就需要了解各种平台的二次开发。且如果业务之间有交互,就会对业务人员编程能力有较高的要求。如果用了Beam,Bang!你只需要学会Beam的编程模式就可以了,底层执行通过配置项来实现,类似 Java 语言的 slogan:「一次开发,到处执行」。
架构设计
这一部分,我们主要介绍 Flink 的组件结构和部署架构,从而对 Flink 的运行有一个基础的认知。
组件栈
Flink是一个分层架构的系统,每一层所包含的组件都提供了特定的抽象,用来服务于上层组件。Flink分层的组件栈如下图所示:
Deployment 层
该层主要涉及了 Flink 的部署模式,Flink 支持多种部署模式:本地、集群(Standalone/YARN)、云(GCE/EC2)。Flink 启动时,会启动一个 JobManager
进程、至少一个 TaskManager
进程,在本地模式下,会在同一个 JVM 内部启动一个 JobManager
进程和 TaskManager
进程。
Runtime 层
Runtime
层提供了支持 Flink 计算的全部核心实现,比如:支持分布式 Stream 处理、JobGraph
到 ExecutionGraph
的映射、调度等等,为上层 API 层提供基础服务。
API层
API 层主要实现了面向无界 Stream 的流处理和面向 Batch 的批处理 API,其中面向流处理对应 DataStream API
,面向批处理对应 DataSet API
。
Libraries层
该层也可以称为 Flink 应用框架层,根据 API 层的划分,在 API 层之上构建的满足特定应用的实现计算框架,也分别对应于面向流处理和面向批处理两类。面向流处理支持:CEP(复杂事件处理)、基于 SQL-like 的操作(基于 Table 的关系操作);面向批处理支持:FlinkML(机器学习库)、Gelly(图处理)。
执行 Flink 时各层的关系:通过各种类库,产生生成 DataStream API 和 DataSet API
的 jar,DataStream
和 DataSet
会单独编译生成 JobGraph
。Runtime 层以 JobGraph
形式接收程序,JobGraph
即为一个一般化的并行数据流图,它拥有任意数量的 Task 来接收和产生 DataStream
。在执行 JobGraph
时,Flink 提供了多种候选部署方案(如 local,remote,YARN等)。
系统架构
Flink 系统的架构与 Spark 类似,是一个基于 Master-Slave
风格的架构,如下图所示:
当 Flink 集群启动后,首先会启动一个 JobManger
和一个或多个的 TaskManager
。由 Client 提交任务给 JobManager
,JobManager
再调度任务到各个 TaskManager
去执行,然后 TaskManager
将心跳和统计信息汇报给 JobManager
。TaskManager
之间以流的形式进行数据的传输。上述三者均为独立的 JVM 进程。JobManager
和 TaskManager
都是通过 Actor 实现的。
这里有三种身份,各自职责简单描述如下:
- Client:会对用户提交的 Flink 程序进行预处理,
并以JobGraph
的形式提交到 Flink 集群(JobManager
)。提交 Job 后,Client 可以结束进程(Streaming 的任务),也可以不结束并等待结果返回; - JobManger:
JobManager
主要负责调度 Job 并协调 Task 做 checkpoint,职责上很像 Storm 的Nimbus
。从 Client 处接收到 Job 和 JAR 包等资源后,会生成优化后的执行计划,并以 Task 的单元调度到各个TaskManager
去执行。 - TaskManager:
TaskManager
在启动的时候就设置好了槽位数(Slot),每个 slot 能启动一个 Task,Task 为线程。从JobManager
处接收需要部署的 Task,部署启动后,与自己的上游建立 Netty 连接,接收数据并处理。
功能详解
功能总结
以下从 流处理特性、API、库支持、部署集成这四个部门总结了 Flink 的特性。
流处理
- 支持高吞吐、低延迟、高性能的流处理
- 支持带有事件时间的窗口(Window)操作
- 支持有状态计算的
Exactly-once
语义 - 支持高度灵活的窗口(Window)操作
- 支持具有反压(
Backpressure
)功能的持续流模型 - 支持基于轻量级分布式快照(Snapshot)实现的容错
- 一个运行时同时支持
Batch on Streaming
处理和Streaming
处理 - Flink 在 JVM 内部实现了自己的内存管理
- 支持迭代和增量迭代计算
- 支持程序自动优化:避免特定情况下 Shuffle、排序等昂贵操作,中间结果有必要进行缓存
API
- 对 Streaming 数据类应用,提供
DataStream API
- 对批处理类应用,提供
DataSet API
(支持Java/Scala)
Libraries
- 支持机器学习(FlinkML)
- 支持图分析(Gelly)
- 支持关系数据处理(Table)
- 支持复杂事件处理(CEP)
集成
- 支持 Flink on YARN
- 支持 HDFS
- 支持来自 Kafka 的输入数据
- 支持 Apache HBase
- 支持 Hadoop 程序
- 支持 Tachyon
- 支持 ElasticSearch
- 支持 RabbitMQ
- 支持 Apache Storm
- 支持 S3
- 支持 XtreemFS
执行图
用户实现的 Flink 程序是由 Stream 和 Transformation 这两个基本构建块组成,其中 Stream 是一个中间结果数据,而 Transformation 是一个操作,它对一个或多个输入 Stream 进行计算处理,输出一个或多个结果 Stream。当一个 Flink 程序被执行的时候,它会被映射为 Streaming Dataflow。一个 Streaming Dataflow 是由一组 Stream
和 Transformation Operator
组成,它类似于一个 DAG
图,在启动的时候从一个或多个** Source Operator
** 开始,结束于一个或多个 **Sink Operator
**。
下面从一个简单的单次统计的例子,看一下 flink 的执行流程:
1 | // get the execution environment |
在上面的例子中,Source Operator
是来自 Socket 输入流,Transformation
有 flatmap
、map
、sum
等,Sink Operator
是输出到控制台(print()
)。
上面的例子,具体又是怎么执行的呢?这需要我们了解 Flink 中的执行图。Flink 中的执行图可以分成四层:StreamGraph
-> JobGraph
-> ExecutionGraph
-> 物理执行图。
- StreamGraph:是根据用户通过
Stream API
编写的代码生成的最初的图。用来表示程序的拓扑结构。 - JobGraph:
StreamGraph
经过优化后生成了JobGraph
,提交给JobManager
的数据结构。主要的优化为:将多个符合条件的节点 chain 在一起作为一个节点,这样可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗。一个JobGraph
是一个 Job 的用户逻辑视图表示,将一个用户要对数据流进行的处理表示为单个DAG
图(对应于JobGraph
),DAG
图由顶点(JobVertex
)和中间结果集(IntermediateDataSet
)组成,其中JobVertex
表示了对数据流进行的转换操作,比如map、flatMap、filter、keyBy等操作,而IntermediateDataSet
是由上游的JobVertex
所生成,同时作为下游的JobVertex
的输入。 - ExecutionGraph:
JobManager
根据JobGraph
生成ExecutionGraph
。ExecutionGraph
是JobGraph
的并行化版本,也就是实际JobManager
调度一个 Job 在TaskManager
上运行的逻辑视图。ExecutionGraph
也是一个DAG
图,是由ExecutionJobVertex
、IntermediateResult
(或IntermediateResultPartition
)组成。ExecutionJobVertex
实际对应于JobGraph
图中的JobVertex
,只不过在ExecutionJobVertex
内部是一种并行表示,由多个并行的ExecutionVertex
所组成。 - 物理执行图:
JobManager
根据ExecutionGraph
对 Job 进行调度后,在各个TaskManager
上部署 Task 后形成的“图”,并不是一个具体的数据结构。
总结一下:StreamGraph
是对用户逻辑的映射,JobGraph
在此基础上进行了一些优化,比如把一部分操作串成 chain 以提高效率。ExecutionGraph
是为了调度存在的,加入了并行处理的概念。而在此基础上真正执行的是 Task 及其相关结构,可以用物理执行图来表达。
在上面「单词统计」的例子里,我们看看 StreamGraph
是什么样子,StreamGraph
数据可以这样输出:
1 | println(env.getExecutionPlan) |
我们会得到一个 Json 串,然后前往 https://flink.apache.org/visualizer/ (建议用 firefox,其他浏览器我试过,会有显示不全的情况出现),贴上 Json 串,即可看到 StreamGraph
的可视化效果了:
从图中我们可以清晰地看到:source/transformation/sink 这三个阶段,同时,不同的并发下,有 REBALANCE
的过程,在同样的并发度下,会进行 FORWARD
。
下面是一个 JobGraph
-> ExecutionGraph
转换的例子:
窗口管理
Flink 支持基于时间窗口操作,也支持基于数据的窗口操作。前者在每个相同的时间间隔对 Stream 中的记录进行处理,通常各个时间间隔内的窗口操作处理的记录数不固定;后者可以在 Stream 中选择固定数量的记录作为一个窗口,对该窗口中的记录进行处理。
在处理 Stream 中的记录时,记录中通常会包含各种典型的时间字段,Flink 支持多种时间的处理,主要有这么几种时间信息:
- Event Time 表示事件创建时间
- Ingestion Time 表示事件进入到 Flink Dataflow 的时间
- Processing Time 表示某个 Operator 对事件进行处理事的本地系统时间(是在TaskManager节点上)。
通常根据 Event Time 会给整个 Streaming 应用带来一定的延迟性,因为在一个基于事件的处理系统中,进入系统的事件可能会基于 Event Time 而发生乱序现象,比如网络,背压对消费速度的影响。
Flink 使用 WaterMark
+ 窗口相结合的机制,来处理乱序事件的问题。WaterMark
是一种特殊的带时间戳的元素,它们会由数据源或是 WaterMark 生成器插入数据流中。具有时间戳 t 的 WaterMark 可以被理解为断言了所有时间戳小于或等于 t 的事件都(在某种合理的概率上)已经到达了,通常我们在接收到 Source 的数据后,应该立刻生成 WaterMark
。
Flink 中的最大乱序时间的设置,需要结合自己的业务以及数据情况。
系统容错
Flink 提供了可以恢复数据流应用到一致状态的容错机制(Checkpoint
机制),Checkpoint
机制利用了一套非常经典的**Chandy-Lamport
算法,它的核心思想是把这个流计算看成一个流式的拓扑,定期从这个拓扑的头部 Source 点开始插入特殊的 Barrie,从上游开始不断的向下游广播这个Barrie。每一个节点收到所有的Barrie,会将 State 做一次 Snapshot**,当每个节点都做完 Snapshot之后,整个拓扑就算完整的做完了一次Checkpoint。接下来不管出现任何故障,都会从最近的 Checkpoint 进行恢复。Flink利用这套经典的算法,保证了强一致性(exactly-once
)的语义。这也是Flink与其他无状态流计算引擎的核心区别。
Barrier 被插入到数据流后,作为数据流的一部分和数据一起向下流动。Barrier 不会干扰正常数据,数据流严格有序。一个 Barrier 把数据流分割成两部分:一部分进入到当前 Snapshot,另一部分进入下一个 Snapshot。每一个 Barrier 都带有快照 Snapshot ID
,并且 Barrier 之前的数据都进入了此快照。Barrier 不会干扰数据流处理,所以非常轻量。多个不同快照的多个 barrier 会在流中同时出现,即多个快照可能同时创建。
如图所示,每当一个 Barrier 流过一个算子节点时,就说明了在该算子上,可以触发一次快照,用以保存当前节点的状态和已经处理过的数据,这就是一份 Snapshot。(在这里可以联想一下spark 的 micro-batch,把 Barrier 想象成分割每个 Batch 的逻辑,会好理解一点)。
两阶段提交 Checkpoint 过程:
阶段1:Barrier 插入 + 传递 + 预提交
- Barrier 插入后,将当前 Snapshot 的位置信息(eg. 消费偏移)发送到
Checkpoint Coordinator
的模块(即 Flink 的JobManager
); - 当一个
Operator
从其输入流接收到所有标识Snapshot n
的 barrier 时,它会向其所有输出流插入一个标识Snapshot n
的 Barrier; Sink Operator
(DAG 流的终点)从其输入流接收到所有Barrier n
时,它向Checkpoint Coordinator
确认Snapshot n
已完成。
阶段二:提交
Checkpoint Coordinator
收到Sink Operator
发出的 Barrier 后,意味着这个 Barrier 和上个 Barrier 之间所夹杂的这批元素已经全部落袋为安,Checkpoint Coordinator
要求所有Operator
删除本次快照内容,以完成清理。
一旦遇到故障,Flink 选择最近一个完成的 Snapshot n
。系统重新部署整个分布式数据流,重置所有 Operator 的状态到 Snapshot n
。数据源被置为从 Sn
位置读取。例如在 Apache Kafka 中,意味着让消费者从 Sn
处偏移开始读取。
需要注意的是,如果接收超过一个输入流,则 Operator 需要基于 Barrier 进行对齐(Stream Aligning
)。Stream Aligning
操作能够实现 exactly-once
语义,但是也会给流处理应用带来延迟,因为为了排列对齐 Barrier,会暂时缓存一部分 Stream 的记录到 Buffer 中。尤其是在数据流并行度很高的场景下可能更加明显,通常以最迟对齐 Barrier 的一个 Stream 为处理Buffer中缓存记录的时刻点。在 Flink 中,提供了一个开关,选择是否使用 Stream Aligning
,如果关掉则 exactly-once
会变成 at-least-once
。
内存管理
Flink 作为一个基于内存(注:下面说的内存,主要指的是 TaskManager
运行时提供的内存资源)的分布式计算引擎,其内存管理模块很大程度上决定了系统的效率和稳定性,尤其对于实时流式计算,JVM GC 带来的微小延迟也有可能被业务感知到。针对这个问题,Flink 实现了一套较为优雅的内存管理机制,可以在引入小量访问成本的情况下提高内存的使用效率并显著降低 GC 成本和 OOM 风险,令用户可以通过少量的简单配置即可建立一个健壮的数据处理系统。
Flink 解决内存问题的核心思想类似于 Spark 的 Project Tungsten
和 HBase 的 BlockCache
:在 JVM 堆内或堆外实现显式的内存管理,即用自定义内存池来进行内存块的分配和回收,并将对象序列化后存储到内存块。因为内存可以被精准地申请和释放,而且序列化的数据占用的空间可以被精确计算,所以组件可以对内存有更好的掌控,这种内存管理方式也被认为是相比 Java 更像 C 语言化的做法。
Flink 将 TaskManager
的运行时 JVM heap 分为 Network Buffers
、MemoryManager
和 Free
三个区域,如下图所示:
- Network Buffers 区:网络模块用于网络传输的一组缓存块对象,单个缓存块对象默认是32KB大小。Flink 会根据 TaskManager 的最大内存来计算该区大小,默认范围是64MB至1GB。
- Memory Manager 区:用于为算子缓存运行时消息记录的大缓存池(比如 Sort、Join 这类耗费大量内存的操作),消息记录会被序列化之后存进这些缓存块对象。这部分区域默认占最大 heap 内存减去 Network Buffers 后的70%,单个缓存块同样默认是32KB。
- Free 区:除去上述两个区域的内存剩余部分便是 Free heap,这个区域用于存放用户代码所产生的数据结构,比如用户定义的 State。
Memory Manager
区由一个个 Memory Segment
组成,它是 Flink 内存管理的核心概念,是在 JVM 内存上的进一步抽象(包括 on-heap 和 off-heap)。不管消息数据实际存储在 on-heap 还在是 off-heap,Flink 都会将它序列化成为一个或多个的 Memory Segment
(内部又称 page)。
Flink 的序列化机制很强大,序列化后的对象存储是非常紧凑的,Flink 序列化器可以描述到消息记录的单个字段,这意味着 Flink 只需要序列化目标字段而不是整个对象。
下面是 Flink 官方对 Object-on-Heap
(直接 Java 对象存储)、Flink-Serialized
(内建序列化器 + 显式内存管理)和 Kryo-Serialized
(Kryo
序列化器 + 显式内存管理)三种方案进行了 GC 表现的对比测试结果,测试场景是对一千万个 Tuple2<Integer, String>
对象进行排序:
总体来说,Flink 在内存管理上较大程度上借鉴了 Spark 的方案,包括存储不足时的溢写机制和内存区域的划分。同时,Flink 还提供了更细粒度的序列化方式,适应 JIT 优化时的数据结构设计等,再这里不再展开。
反压感知
Backpressure
在流式计算系统中会比较受到关注。因为在一个 Stream 上进行处理的多个 Operator 之间,它们处理速度和方式可能非常不同,所以就存在上游 Operator 如果处理速度过快,下游 Operator 处可能机会堆积 Stream 记录,严重会造成处理延迟或下游 Operator 负载过重而崩溃(有些系统可能会丢失数据)。
Flink Web 界面上提供了对运行 Job 的 Backpressure
行为的监控,它通过使用 Sampling 线程对正在运行的 Task 进行堆栈跟踪采样来实现:
JobManager
会每间隔50ms触发对一个 Job 的每个 Task 依次进行100次堆栈跟踪调用,根据调用调用结果来计算得到一个比值(Radio)来确定当前运行 Job 的 Backpressure
状态,状态分为 OK: 0 <= Ratio <= 0.10
,LOW: 0.10 < Ratio <= 0.5
,HIGH: 0.5 < Ratio <= 1
这三种,我们可以在Web界面上可以看到这个 Radio 值。
性能对比
这里简单对比下 spark、storm、flink 三款流/批处理引擎在架构设计、数据处理性能上的不同。
还有一篇文章介绍的也很详细,感兴趣的可以参考: Hadoop vs Spark vs Flink – Big Data Frameworks Comparison。
架构对比
storm
简述:Storm集群采用的是 master-slaver 模型,Nimbus
是运行于主节点上的守护进程,Supervisor
是运行在子节点上的守护进程。客户端提交整体 Topology 到 Nimbus
,主节点查询 Zookeeper
中子节点的资源信息,开始分配任务,并将任务与子节点的对应关系存放在 Zookeeper
中。子节点 Supervisor
查询 Zookeeper
中的信息领取任务,分配具体的 worker 以及 executors 执行具体的 tasks。
顺便提一句,storm 进行流消费时实现的是 at-least-once
语义。
spark
简述:Spark集 群采用的是 master-slave 模型,master(CluserManager
)负责集群整体资源的调度和管理并管理 worker,worker 管理其上的 executor,driver 完成用户的上下文的创建,并接受 master 的指示将 task 发送到 worker 节点上进行执行。
flink
Flink 部署模式演进:
- Standalone:独立集群,多 job 混合执行,集群大小固定,同时隔离性较差;
- **Flink on YARN(session)**:允许运行多个作业在同一个 Flink 集群中,代价是 job 之间没有资源隔离(同一个
TaskManager
中可能跑多个不同 job 的 task); - **Flink on YARN(per-job)**:通过 YARN 为每一个 Flink job 都分配一个单独的 Flink 集群,这样就解决了不同作业之间的资源隔离问题;
- Flink on Mesos:支持 Mesos 进行调度管理;
- Flink on Kubernetes:支持 Docker/Kubernetes 进行调度管理。
之所以有这么的多的部署模式,是考虑了 job 执行时的:隔离性、拓展性、伸缩性、可用性的一个结果。那有没有一个比较同意的部署架构呢?那就是 FLIP-6 架构了,在该架构下,支持上述所有的部署模式,同时还带来了一些比较新的特性(需要详细了解 FLIP-6 的请戳我:FLIP-6 - Flink Deployment and Process Model - Standalone, Yarn, Mesos, Kubernetes, etc.)。
Flink 是从1.5版本开始默认使用 FLIP-6 架构的,FLIP-6支持 YARN,Mesos 以及独立模式,此外还可以在容器化环境中运行Flink。在Yarn/Mesos/Kubernetes 模式下,允许动态分配和释放资源。
可以看到,上面的架构中除了我们熟知的JobManager
、TaskManager
、ResourceManager
,还有一个新的角色:Dispatcher
。Dispatcher
是 FLIP-6 设计里面引入的一个新概念,它的主要职责是接收从Client 端提交过来的 job,并生成一个 JobManager
去负责这个 job 在集群资源管理器上执行。
引入 Dispatcher
的原因是:
- 某些 Flink 集群资源管理器需要一个中控的角色来负责 job 生成和监控;
- Standalone 模式下需要实现一个角色来实现旧
JobManager
的功能——等待作业提交。
其他角色功能:
JobManager
:基本和之前一致,负责**一个 job **的整个生命周期管理。TaskManager
:基本和之前一致,它会同 ResourceManager
& JobManager
进行通信,上报心跳和资源信息(比如上报 slot 使用信息给 JobManager
,以便后者进行资源管理)。ResourceManager
:集群资源管理器,针对不同的集群管理(eg. YARN)有不同的实现,主要负责对整个集群资源的分配与管理。主要职责:
- 通过启动 container 来创建新的
TaskManager
(or 更细粒度的 slot)资源,再将其分配给 job; - 给
JobManager
和TaskManager
发送异常通知消息; - 缓存
TaskManager
(container)资源以便重复利用,并将一段时间未被使用的空闲资源释放。
例子:
例1:基于 YARN 的部署架构:
ResourceManager
和 JobManager
运行在 ApplicationMaster
进程内,进程异常检测以及 HA 由 YARN 负责。在此架构下,client 可以直接在 YARN 上面提交一个 job(不再像以前需要先启动一个固定大小的Flink 集群,然后把作业提交到这个Flink集群上)。job 提交后,可以按需申请容器(指被同一个作业的不同算子所使用的容器可以有不同的 CPU/Memory 配置),没有被使用的容器将会被释放。
例2:基于 K8s 的部署架构:
在此模式下, 整个 Flink 集群完全是由基于 Docker 的 JobManager
和 TaskManager
组成,不存在客户端,也不涉及 job 提交步骤。
流处理性能
在流程性能对比这块,我们只对比 Storm 和 Flink,具体地可以参考这篇文章:流计算框架 Flink 与 Storm 的性能对比,一些重要的结论如下:
- Storm 单线程吞吐约为 8.7 万条/秒,Flink 单线程吞吐可达 35 万条/秒。Flink 吞吐约为 Storm 的 3-5 倍。Storm QPS 接近吞吐时延迟(含 Kafka 读写时间)中位数约 100 毫秒,99 线约 700 毫秒,Flink 中位数约 50 毫秒,99 线约 300 毫秒;
- Flink 在满吞吐时的延迟约为 Storm 的一半,且随着 QPS 逐渐增大,Flink 在延迟上的优势开始体现出来。但是,如果用户逻辑本身耗时长,Flink 和 Storm 的框架的差异会缩小;
- Flink
Exactly Once
的吞吐较 At Least Once 而言下降 6.3%,延迟差异不大;StormAt Most Once
语义下的吞吐较At Least Once
提升 16.8%,延迟稍有下降(由于 Storm 会对每条消息进行 ACK,Flink 是基于一批消息做的检查点,不同的实现原理导致两者在At Least Once
语义的花费差异较大,从而影响了性能)。
Spark 是基于数据片集合(RDD)进行小批量处理的,它只能支持秒级计算,所以 Spark 在流式处理方面,不可避免会增加一些延时。Flink 是一行一行的,它的流式计算跟 Storm 的性能差不多,是支持毫秒级计算的。
批处理性能
目前,对比 Flink 和 Spark 批处理性能的的不太多,因为 Spark 的性能(吞吐,性能)受 beachmark 的影响太大,如 batch size 的选择。Performance Comparison of Streaming Big Data Platforms 这里有个简单的测评,结论是:当批处理的 batch size 增加,Spark 在吞吐量上是优于 Flink 的。
这篇论文(Spark Versus Flink: Understanding Performance in Big Data Analytics Frameworks)里提到:在进行大规模图计算时,Spark 比 Flink 快 1.7 倍。
生态对比
类库生态
目前来看,Spark 的在类库上支持要更好一些。
社区发展
相同点:Spark 与 Flink 均有社区支持。
相异点:Spark 社区活跃度比 Flink 高很多。
场景选择
以下需要高吞吐,对时延不敏感的场景建议考虑使用 Spark:
- 大规模图计算
- 机器学习(需要迭代)
以下实时计算场景建议考虑使用 Flink :
- 要求消息投递语义为 Exactly Once 的场景;
- 数据量较大,要求高吞吐低延迟的场景;
- 需要进行状态管理或窗口统计的场景。
总结补充
总结
现有的开源计算方案,会把流处理和批处理作为两种不同的应用类型,因为它们所提供的 SLA 是完全不相同的:流处理一般需要支持低延迟、exactly-once
保证,而批处理需要支持高吞吐、高效处理。
Flink 从另一个视角看待流处理和批处理,将二者统一起来:Flink 是完全支持流处理,也就是说作为流处理看待时输入数据流是无界的;批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。用批来模拟流有一定的技术局限性,并且这个局限性可能很难突破。比如说:
- 用批来模拟流的方式使得一些需要跨 batch 的操作变得非常困难,例如
session window
;用户不得不自己想办法去实现相关逻辑; - 用批来模拟流的模式很难做好
Backpressure
。当一个 batch 因为种种原因处理慢了,那么下一个 batch 要么不得不容纳更多的新来数据,要么不得不堆积更多的 batch,整个任务可能会被拖垮,这是一个非常致命的问题。
在内存管理上,虽然 Spark 和 Flink 的内存设计思路很相似,但还是有很大的不同,Flink 把内存从应用层剥离的更彻底一些。Spark 的序列化存储是以 RDD 的一个 Partiton 为单位,而 Flink 的序列化则是以消息记录为单位,因此后者出现 OOM
的概率会低一些。
Blink
贡献
1. 重构了 Flink 的分布式架构,将 Flink 的 Job 调度和资源管理做了一个清晰的分层和解耦。
这样做的首要好处是 Flink 可以原生的跑在各种不同的开源资源管理器上。经过这套分布式架构的改进,Flink 可以原生地跑在 Hadoop Yarn
和 Kubernetes
这两个最常见的资源管理系统之上。同时将 Flink 的任务调度从集中式调度改为了分布式调度,这样Flink就可以支持更大规模的集群,以及得到更好的资源隔离。
2. 实现了增量的 Checkpoint 机制。
因为 Flink 提供了有状态的计算和定期的 Checkpoint
机制,如果内部的数据越来越多,不停地做Checkpoint
,Checkpoint
会越来越大,最后可能导致做不出来。提供了增量的 Checkpoint 后,Flink 会自动地发现哪些数据是增量变化,哪些数据是被修改了。同时只将这些修改的数据进行持久化。这样 Checkpoint
不会随着时间的运行而越来越难做,整个系统的性能会非常地平稳,这也是我们贡献给社区的一个很重大的特性。
规划
统一 API Stack:在 Runtime之上,将采用一个 DAG
(有限无环图)API,作为一个批流统一的 API 层,统一 DataStream
和 DataSet
,批计算和流计算不需要泾渭分明的表达出来。只需要让开发者在不同的节点,不同的边上定义不同的属性,来规划数据是流属性还是批属性;
统一 SQL方案:将流计算和批计算这两种源的查询,都都模拟成数据表查询,即不管是流式 SQL,还是批处理 SQL,都可以用同一个 Query 来表达复用。这样以来流批都可以用同一个 Query 优化或者解析。甚至很多流和批的算子都是可以复用的。