一,抽象层次
Flink提供不同级别的抽象来开发流/批处理应用程序。
1,stateful streaming
最底层。它通过Process Function嵌入到DataStream API中。它允许用户从一个或多个流自由处理事件,并使用一致的容错状态。此外,用户可以注册事件时间和处理时间回调,允许程序实现复杂的计算。
2,Core APIs
实际上,大多数应用程序不需要上述的低级别抽象,而是针对Core API(如DataStream API(有界/无界流))和DataSet API(有界数据集)进行编程。这些流畅的API为数据处理提供了常见的构建模块,如用户指定的各种转换形式,连接,聚合,窗口,状态等。在这些API中处理的数据类型以各自的编程语言表示为classes。底层的Process Function和DataStream API的整合,使得针对一些特定的操作可以实现更低层次的抽象。DataSet API为有界数据集提供了额外的原函数,如循环/迭代。
3,Table API
Table API是以表为中心的声明式DSL,可能是动态更改表(表示流时)。Table API遵循(扩展)关系模型:Table 具有附加schema(与关系数据库中的表相似),API提供操作,例如select,project,join,group-by,aggregate等。Table API代表的是应该做什么逻辑操作,而不是直接指定如何编写操作的源代码。虽然Table API可以通过各种类型的用户定义的函数进行扩展,但它不如Core API那么具有表达力,但使用起来更加简洁(少写很多代码)。
此外,Table API程序还可以通过在执行之前应用优化规则的优化器。
可以在表和DataStream / DataSet之间无缝转换,允许程序将Table API和DataStream和DataSet API混合使用。
4,SQL
最高层次的抽象就是SQL。无论是语法还是表达,该层次的抽象都很像Table API。SQL抽象与Table API紧密交互,SQL查询可以在Table API中定义的表上执行。
二,Programs and Dataflows
Flink程序的基本构建块是流和转换。在概念上,stream 是data records的(潜在的永无止境的)flow,并且变换是将一个或多个流作为输入的操作,并且作为结果产生一个或多个输出流。
执行时,Flink程序被映射成streaming dataflows,由streams 和转换操作符组成。每个dataflow 从一个或多个sources开始,并以一个或多个sinks结束。dataflows 像任意的有向无环图(DAG)。虽然通过迭代构造允许特殊形式的循环,但是为了简单起见,我们大部分都会任务是DAG。
通常,程序中的变换和数据流中的运算符之间存在一对一的对应关系。然而,有时,一个变换可能由多个转换算子组成。
三,Parallel Dataflows
Flink中的程序本质上是并行和分发的。在执行期间,流具有一个或多个流分区,并且每个运算符具有一个或多个运算符subtask。操作符subtask彼此独立,并以不同的线程执行,可能在不同的机器或容器上执行。
运算符子任务的数量是该特定操作符的并行性。stream 的并行性总是其生产运算符的并行性。同一程序的不同运算符可能具有不同的并行级别。
Streams 可以以一对一(或转发)模式或重新分配模式在两个运算符之间传输数据:
1),一对一
One-to-one streams(例如上图中的Source和map()运算符之间)保留元素的分区和ordering。这意味着map()运算符的子任务[1]看到的元素的顺序将和由Source 操作符产生的顺序相同。
2),再分配
重新分配流(如map()和上面的keyBy / window之间以及keyBy / window和Sink之间)改变流的分区。每个操作符子任务根据所选择的转换将数据发送到不同的目标子任务。
KeyBy是按照key的hash值进行重新分区,rebalance()是以随机的方式重新分区。在重新分配交换中,元素之间的排序仅保留在每对发送和接收子任务对(例如,keyBy / window的map()和子任务[2]的子任务[1]中)。上例子中仅保证相同key的元素顺序性。
四,Windows
Aggregating events(例如,counts,sums)在流上的工作方式与批处理不同。例如,不可能对流中的所有元素进行计数,因为流通常是无限的(无界)。相反,流上的聚合(计数,总和等)由窗口限定,例如“最后5分钟计数”或“最后100个元素的总和”。
Windows可以时间驱动(例如:每30秒)或数据驱动(例如:每100个元素)。通常区分不同类型的窗口,例如翻转窗口(无重叠)[tumbling windows (no overlap)],滑动窗口(具有重叠)[sliding windows (with overlap)]和会话窗口(由不活动空隙划分)[ession windows (punctuated by a gap of inactivity)]。
五,Time
当在Streaming 程序中使用时间的时候,如定义窗口时,可以参考不同的时间概念:
1,Event Time
Event Time是Event创建的时间。Events中的Event Time是以时间戳的格式存在。Fink通过timestamp assigners来获得event timestamps。timestamp assigners后面出文章具体说。
2,Ingestion time
Ingestion time是event进入flink dataflow的时间。
3,Processing Time
Processing Time是基于时间的操作算子执行的本地时间。
六,Stateful Operations
虽然dataflow 中的许多操作只是一次处理一个单独的事件(例如事件解析器),但一些操作会记住跨多个事件的信息(例如窗口运算符)。这些操作称为有状态。有状态的操作算子,状态保存在嵌入式的键/值存储中。状态会和被状态操作算子读取的streams一起分区和分配。使用keyBy函数后,仅keyed streams可能获取key/value状态,并且仅限于与当前事件key相关的值。对齐stream和state的keys,确保所有状态更新都是本地操作,保证一致性,无需事务开销。此对齐还允许Flink重新分配状态并透明地调整流分区。
七,Checkpoints
Flink使用stream replay和checkpoint组合来实现容错。检查点与每个输入流中的特定点相关联,以及每个运算符的相应状态。Streaming dataflow可以从检查点恢复流,同时通过恢复操作符的状态,从检查点重新执行事件来保持一致性(一次性处理语义)。
检查点间隔是在恢复时间(需要重新计算的事件数)的情况下,在执行期间消除容错的开销的一种手段。
八,Batch on Streaming
Flink执行批处理程序作为流程序的特殊情况,其中streams 是有限的(有限数量的元素)。DataSet在内部被视为数据流。以上概念因此适用于批处理程序,同样适用于流式传输程序,但有一些例外:
1,批处理程序的容错不使用checkpoint。恢复需要完全执行流。这是可能的,因为输入是有限的。恢复成本增加,但是使得处理变得廉价,因为无需进行checkpoint。
2,DataSet API中的有状态操作使用简化的in-memory/out-of-core数据结构,而不是键/值索引。
3,DataSet API引入了特殊的同步(superstep-based)迭代,这些迭代只能在有界流上进行。具体后面出文章介绍。
九,Tasks and Operator Chains
为了分布式执行,Flink 链式合并operator subtasks成tasks。每个task被一个线程执行。链式的将操作符合并成任务是一个很有用的优化:它减少了线程到线程切换和缓冲的开销,并且在减少延迟的同时增加了总体吞吐量。链式操作是可以配置的(后面出文章具体介绍)。
下图dataflow的例子,使用五个subtasks执行,于是需要五个并发线程。
十,flink的角色
Flink运行时由两种类型的进程组成:
1),JobManager也叫master协调分布式执行。他们调度任务,协调checkpoints,协调故障恢复等。至少有一个JobManager。高可用情况下可以启动多个JobManager,其中一个选举为leader,其余为standby。
2),TaskManager也叫worker,负责执行具体的tasks。缓存,交换数据流。至少有一个TaskManager。
JobManager和TaskManager可以用很多种方式启动:可以直接作为Standalone集群启动,也可以被yarn或者Mesos管理。TaskManager连接到JobManager,宣布自己可用,并接受分配的工作。
客户端不是runtime 和程序执行的一部分,而是用于准备并发送数据流到JobManager。
之后,客户端可以断开连接或保持连接以接收进度报告。客户端作为触发执行的Java / Scala程序的一部分运行,或在命令行进程中运行./bin/flink运行。。
十一,Task Slots and Resources
每个worker(或者叫TaskManager)是一个jvm进程,可以在独立的线程中执行一个或者多个subtasks。为了控制worker接受tasks数,worker称之为任务槽数(至少有一个)。
每个task slot代表TaskManager的一个固定部分资源。例如,一个TaskManager有三个slot,会均分其管理的内存给每个slot。按槽分配资源意味着一个subtask不会与其它job的subtask竞争管理的内存,而是使用保留的内存资源。这里并没有实现cpu资源的隔离。仅仅实现了基于内存的资源隔离。
通过调整task slots的数量,用户可以定义子任务彼此隔离的方式。每个TaskManager拥有一个slot 意味着每个任务组在单独的JVM中运行(例如,可以在单独的容器中启动)。拥有多个slots 意味着更多的子任务共享相同的JVM。相同JVM中的任务共享TCP连接(通过复用)和心跳消息。他们还可以共享数据集和数据结构,从而减少每个任务的开销。
默认情况下,Flink允许子任务共享slot,即使它们是不同tasks的subtasks,只要它们来自相同的job。结果是一个slot可以处理整个job pipeline。允许这个slot共享有两个主要好处:
1),Flink集群需要与job中使用的最高并行度完全相同的task slot数。不需要计算一个程序总共包含多少任务(具有不同的并行性)。
2),更容易获得更好的资源利用率。没有slot共享,非密集的source / map()子任务将拥有与资源密集型窗口子任务一样多的资源。使用slot共享,通过将例子中国的基础并行度从2增加到6,可以充分利用slot资源,同时保证heavy subtasks均匀的分配到不同taskmanager中。
也有resource 组机制,可以组织不希望的slot共享。
作为经验法则,task slot最佳默认数量将是CPU内核的数量。
十二,State Backends
key/values索引存储的确切数据结构取决于所选的State Backends(目前有三种:MemoryStateBackend,FsStateBackend,RocksDBStateBackend)。一种state backend将数据存储于内存的hash map中,另一种state backend使用的是RocksDB,还有就是使用文件。除了定义保存状态的数据结构之外,state backends 还实现逻辑以获取键/值状态的 point-in-time 快照,并将该快照存储为checkpoint的一部分。
十三,Savepoints
使用Data Stream API编写的程序可以从Savepoints恢复执行。Savepoints允许更新程序和Flink集群,而不会丢失任何状态。
Savepoints 是手动触发的checkpoints,它们记录程序的快照并将其写入状态后端。他们依靠这个常规的检查点机制。执行过程中,定期在工作节点上快照并生成检查点。为了恢复,只需要最后完成的检查点,一旦新的检查点完成,可以安全地丢弃较旧的检查点。Savepoints 与这些定期checkpoints类似,除了它们由用户触发,并且在较新的检查点完成时不会自动过期。可以从命令行创建保存点,也可以通过REST API取消作业。
十四,总结
Flink作为一个流式处理的框架,在实时计算中也是很常见的。Flink应用程序总共有以下三个部分:
1),Data source:Flink处理的输入数据
2),Transformations:Flink修改传入数据时的处理步骤
3),Data sink: flink处理结束后输出位置